Squashed 'yocto-poky/' content from commit ea562de

git-subtree-dir: yocto-poky
git-subtree-split: ea562de57590c966cd5a75fda8defecd397e6436
diff --git a/bitbake/lib/bb/COW.py b/bitbake/lib/bb/COW.py
new file mode 100644
index 0000000..6917ec3
--- /dev/null
+++ b/bitbake/lib/bb/COW.py
@@ -0,0 +1,323 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# This is a copy on write dictionary and set which abuses classes to try and be nice and fast.
+#
+# Copyright (C) 2006 Tim Amsell
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#Please Note:
+# Be careful when using mutable types (ie Dict and Lists) - operations involving these are SLOW.
+# Assign a file to __warn__ to get warnings about slow operations.
+#
+
+from __future__ import print_function
+import copy
+import types
+ImmutableTypes = (
+    types.NoneType,
+    bool,
+    complex,
+    float,
+    int,
+    long,
+    tuple,
+    frozenset,
+    basestring
+)
+
+MUTABLE = "__mutable__"
+
+class COWMeta(type):
+    pass
+
+class COWDictMeta(COWMeta):
+    __warn__ = False
+    __hasmutable__ = False
+    __marker__ = tuple()
+
+    def __str__(cls):
+        # FIXME: I have magic numbers!
+        return "<COWDict Level: %i Current Keys: %i>" % (cls.__count__, len(cls.__dict__) - 3)
+    __repr__ = __str__
+
+    def cow(cls):
+        class C(cls):
+            __count__ = cls.__count__ + 1
+        return C
+    copy = cow
+    __call__ = cow
+
+    def __setitem__(cls, key, value):
+        if not isinstance(value, ImmutableTypes):
+            if not isinstance(value, COWMeta):
+                cls.__hasmutable__ = True
+            key += MUTABLE
+        setattr(cls, key, value)
+
+    def __getmutable__(cls, key, readonly=False):
+        nkey = key + MUTABLE
+        try:
+            return cls.__dict__[nkey]
+        except KeyError:
+            pass
+
+        value = getattr(cls, nkey)
+        if readonly:
+            return value
+
+        if not cls.__warn__ is False and not isinstance(value, COWMeta):
+            print("Warning: Doing a copy because %s is a mutable type." % key, file=cls.__warn__)
+        try:
+            value = value.copy()
+        except AttributeError as e:
+            value = copy.copy(value)
+        setattr(cls, nkey, value)
+        return value
+
+    __getmarker__ = []
+    def __getreadonly__(cls, key, default=__getmarker__):
+        """\
+        Get a value (even if mutable) which you promise not to change.
+        """
+        return cls.__getitem__(key, default, True)
+
+    def __getitem__(cls, key, default=__getmarker__, readonly=False):
+        try:
+            try:
+                value = getattr(cls, key)
+            except AttributeError:
+                value = cls.__getmutable__(key, readonly)
+
+            # This is for values which have been deleted
+            if value is cls.__marker__:
+                raise AttributeError("key %s does not exist." % key)
+
+            return value
+        except AttributeError as e:
+            if not default is cls.__getmarker__:
+                return default
+
+            raise KeyError(str(e))
+
+    def __delitem__(cls, key):
+        cls.__setitem__(key, cls.__marker__)
+
+    def __revertitem__(cls, key):
+        if not cls.__dict__.has_key(key):
+            key += MUTABLE
+        delattr(cls, key)
+
+    def __contains__(cls, key):
+        return cls.has_key(key)
+
+    def has_key(cls, key):
+        value = cls.__getreadonly__(key, cls.__marker__)
+        if value is cls.__marker__:
+            return False
+        return True
+
+    def iter(cls, type, readonly=False):
+        for key in dir(cls):
+            if key.startswith("__"):
+                continue
+
+            if key.endswith(MUTABLE):
+                key = key[:-len(MUTABLE)]
+
+            if type == "keys":
+                yield key
+
+            try:
+                if readonly:
+                    value = cls.__getreadonly__(key)
+                else:
+                    value = cls[key]
+            except KeyError:
+                continue
+
+            if type == "values":
+                yield value
+            if type == "items":
+                yield (key, value)
+        raise StopIteration()
+
+    def iterkeys(cls):
+        return cls.iter("keys")
+    def itervalues(cls, readonly=False):
+        if not cls.__warn__ is False and cls.__hasmutable__ and readonly is False:
+            print("Warning: If you arn't going to change any of the values call with True.", file=cls.__warn__)
+        return cls.iter("values", readonly)
+    def iteritems(cls, readonly=False):
+        if not cls.__warn__ is False and cls.__hasmutable__ and readonly is False:
+            print("Warning: If you arn't going to change any of the values call with True.", file=cls.__warn__)
+        return cls.iter("items", readonly)
+
+class COWSetMeta(COWDictMeta):
+    def __str__(cls):
+        # FIXME: I have magic numbers!
+        return "<COWSet Level: %i Current Keys: %i>" % (cls.__count__, len(cls.__dict__) -3)
+    __repr__ = __str__
+
+    def cow(cls):
+        class C(cls):
+            __count__ = cls.__count__ + 1
+        return C
+
+    def add(cls, value):
+        COWDictMeta.__setitem__(cls, repr(hash(value)), value)
+
+    def remove(cls, value):
+        COWDictMeta.__delitem__(cls, repr(hash(value)))
+
+    def __in__(cls, value):
+        return COWDictMeta.has_key(repr(hash(value)))
+
+    def iterkeys(cls):
+        raise TypeError("sets don't have keys")
+
+    def iteritems(cls):
+        raise TypeError("sets don't have 'items'")
+
+# These are the actual classes you use!
+class COWDictBase(object):
+    __metaclass__ = COWDictMeta
+    __count__ = 0
+
+class COWSetBase(object):
+    __metaclass__ = COWSetMeta
+    __count__ = 0
+
+if __name__ == "__main__":
+    import sys
+    COWDictBase.__warn__ = sys.stderr
+    a = COWDictBase()
+    print("a", a)
+
+    a['a'] = 'a'
+    a['b'] = 'b'
+    a['dict'] = {}
+
+    b = a.copy()
+    print("b", b)
+    b['c'] = 'b'
+
+    print()
+
+    print("a", a)
+    for x in a.iteritems():
+        print(x)
+    print("--")
+    print("b", b)
+    for x in b.iteritems():
+        print(x)
+    print()
+
+    b['dict']['a'] = 'b'
+    b['a'] = 'c'
+
+    print("a", a)
+    for x in a.iteritems():
+        print(x)
+    print("--")
+    print("b", b)
+    for x in b.iteritems():
+        print(x)
+    print()
+
+    try:
+        b['dict2']
+    except KeyError as e:
+        print("Okay!")
+
+    a['set'] = COWSetBase()
+    a['set'].add("o1")
+    a['set'].add("o1")
+    a['set'].add("o2")
+
+    print("a", a)
+    for x in a['set'].itervalues():
+        print(x)
+    print("--")
+    print("b", b)
+    for x in b['set'].itervalues():
+        print(x)
+    print()
+
+    b['set'].add('o3')
+
+    print("a", a)
+    for x in a['set'].itervalues():
+        print(x)
+    print("--")
+    print("b", b)
+    for x in b['set'].itervalues():
+        print(x)
+    print()
+
+    a['set2'] = set()
+    a['set2'].add("o1")
+    a['set2'].add("o1")
+    a['set2'].add("o2")
+
+    print("a", a)
+    for x in a.iteritems():
+        print(x)
+    print("--")
+    print("b", b)
+    for x in b.iteritems(readonly=True):
+        print(x)
+    print()
+
+    del b['b']
+    try:
+        print(b['b'])
+    except KeyError:
+        print("Yay! deleted key raises error")
+
+    if b.has_key('b'):
+        print("Boo!")
+    else:
+        print("Yay - has_key with delete works!")
+
+    print("a", a)
+    for x in a.iteritems():
+        print(x)
+    print("--")
+    print("b", b)
+    for x in b.iteritems(readonly=True):
+        print(x)
+    print()
+
+    b.__revertitem__('b')
+
+    print("a", a)
+    for x in a.iteritems():
+        print(x)
+    print("--")
+    print("b", b)
+    for x in b.iteritems(readonly=True):
+        print(x)
+    print()
+
+    b.__revertitem__('dict')
+    print("a", a)
+    for x in a.iteritems():
+        print(x)
+    print("--")
+    print("b", b)
+    for x in b.iteritems(readonly=True):
+        print(x)
+    print()
diff --git a/bitbake/lib/bb/__init__.py b/bitbake/lib/bb/__init__.py
new file mode 100644
index 0000000..1f7946e
--- /dev/null
+++ b/bitbake/lib/bb/__init__.py
@@ -0,0 +1,142 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Build System Python Library
+#
+# Copyright (C) 2003  Holger Schurig
+# Copyright (C) 2003, 2004  Chris Larson
+#
+# Based on Gentoo's portage.py.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+__version__ = "1.27.1"
+
+import sys
+if sys.version_info < (2, 7, 3):
+    raise RuntimeError("Sorry, python 2.7.3 or later is required for this version of bitbake")
+
+
+class BBHandledException(Exception):
+    """
+    The big dilemma for generic bitbake code is what information to give the user
+    when an exception occurs. Any exception inheriting this base exception class
+    has already provided information to the user via some 'fired' message type such as
+    an explicitly fired event using bb.fire, or a bb.error message. If bitbake 
+    encounters an exception derived from this class, no backtrace or other information 
+    will be given to the user, its assumed the earlier event provided the relevant information.
+    """
+    pass
+
+import os
+import logging
+
+
+class NullHandler(logging.Handler):
+    def emit(self, record):
+        pass
+
+Logger = logging.getLoggerClass()
+class BBLogger(Logger):
+    def __init__(self, name):
+        if name.split(".")[0] == "BitBake":
+            self.debug = self.bbdebug
+        Logger.__init__(self, name)
+
+    def bbdebug(self, level, msg, *args, **kwargs):
+        return self.log(logging.DEBUG - level + 1, msg, *args, **kwargs)
+
+    def plain(self, msg, *args, **kwargs):
+        return self.log(logging.INFO + 1, msg, *args, **kwargs)
+
+    def verbose(self, msg, *args, **kwargs):
+        return self.log(logging.INFO - 1, msg, *args, **kwargs)
+
+logging.raiseExceptions = False
+logging.setLoggerClass(BBLogger)
+
+logger = logging.getLogger("BitBake")
+logger.addHandler(NullHandler())
+logger.setLevel(logging.DEBUG - 2)
+
+# This has to be imported after the setLoggerClass, as the import of bb.msg
+# can result in construction of the various loggers.
+import bb.msg
+
+from bb import fetch2 as fetch
+sys.modules['bb.fetch'] = sys.modules['bb.fetch2']
+
+# Messaging convenience functions
+def plain(*args):
+    logger.plain(''.join(args))
+
+def debug(lvl, *args):
+    if isinstance(lvl, basestring):
+        logger.warn("Passed invalid debug level '%s' to bb.debug", lvl)
+        args = (lvl,) + args
+        lvl = 1
+    logger.debug(lvl, ''.join(args))
+
+def note(*args):
+    logger.info(''.join(args))
+
+def warn(*args):
+    logger.warn(''.join(args))
+
+def error(*args, **kwargs):
+    logger.error(''.join(args), extra=kwargs)
+
+def fatal(*args, **kwargs):
+    logger.critical(''.join(args), extra=kwargs)
+    raise BBHandledException()
+
+def deprecated(func, name=None, advice=""):
+    """This is a decorator which can be used to mark functions
+    as deprecated. It will result in a warning being emitted
+    when the function is used."""
+    import warnings
+
+    if advice:
+        advice = ": %s" % advice
+    if name is None:
+        name = func.__name__
+
+    def newFunc(*args, **kwargs):
+        warnings.warn("Call to deprecated function %s%s." % (name,
+                                                             advice),
+                      category=DeprecationWarning,
+                      stacklevel=2)
+        return func(*args, **kwargs)
+    newFunc.__name__ = func.__name__
+    newFunc.__doc__ = func.__doc__
+    newFunc.__dict__.update(func.__dict__)
+    return newFunc
+
+# For compatibility
+def deprecate_import(current, modulename, fromlist, renames = None):
+    """Import objects from one module into another, wrapping them with a DeprecationWarning"""
+    import sys
+
+    module = __import__(modulename, fromlist = fromlist)
+    for position, objname in enumerate(fromlist):
+        obj = getattr(module, objname)
+        newobj = deprecated(obj, "{0}.{1}".format(current, objname),
+                            "Please use {0}.{1} instead".format(modulename, objname))
+        if renames:
+            newname = renames[position]
+        else:
+            newname = objname
+
+        setattr(sys.modules[current], newname, newobj)
+
diff --git a/bitbake/lib/bb/build.py b/bitbake/lib/bb/build.py
new file mode 100644
index 0000000..948c395
--- /dev/null
+++ b/bitbake/lib/bb/build.py
@@ -0,0 +1,770 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake 'Build' implementation
+#
+# Core code for function execution and task handling in the
+# BitBake build tools.
+#
+# Copyright (C) 2003, 2004  Chris Larson
+#
+# Based on Gentoo's portage.py.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Based on functions from the base bb module, Copyright 2003 Holger Schurig
+
+import os
+import sys
+import logging
+import shlex
+import glob
+import time
+import stat
+import bb
+import bb.msg
+import bb.process
+from contextlib import nested
+from bb import event, utils
+
+bblogger = logging.getLogger('BitBake')
+logger = logging.getLogger('BitBake.Build')
+
+NULL = open(os.devnull, 'r+')
+
+__mtime_cache = {}
+
+def cached_mtime_noerror(f):
+    if f not in __mtime_cache:
+        try:
+            __mtime_cache[f] = os.stat(f)[stat.ST_MTIME]
+        except OSError:
+            return 0
+    return __mtime_cache[f]
+
+def reset_cache():
+    global __mtime_cache
+    __mtime_cache = {}
+
+# When we execute a Python function, we'd like certain things
+# in all namespaces, hence we add them to __builtins__.
+# If we do not do this and use the exec globals, they will
+# not be available to subfunctions.
+__builtins__['bb'] = bb
+__builtins__['os'] = os
+
+class FuncFailed(Exception):
+    def __init__(self, name = None, logfile = None):
+        self.logfile = logfile
+        self.name = name
+        if name:
+            self.msg = 'Function failed: %s' % name
+        else:
+            self.msg = "Function failed"
+
+    def __str__(self):
+        if self.logfile and os.path.exists(self.logfile):
+            msg = ("%s (log file is located at %s)" %
+                   (self.msg, self.logfile))
+        else:
+            msg = self.msg
+        return msg
+
+class TaskBase(event.Event):
+    """Base class for task events"""
+
+    def __init__(self, t, logfile, d):
+        self._task = t
+        self._package = d.getVar("PF", True)
+        self.taskfile = d.getVar("FILE", True)
+        self.taskname = self._task
+        self.logfile = logfile
+        self.time = time.time()
+        event.Event.__init__(self)
+        self._message = "recipe %s: task %s: %s" % (d.getVar("PF", True), t, self.getDisplayName())
+
+    def getTask(self):
+        return self._task
+
+    def setTask(self, task):
+        self._task = task
+
+    def getDisplayName(self):
+        return bb.event.getName(self)[4:]
+
+    task = property(getTask, setTask, None, "task property")
+
+class TaskStarted(TaskBase):
+    """Task execution started"""
+    def __init__(self, t, logfile, taskflags, d):
+        super(TaskStarted, self).__init__(t, logfile, d)
+        self.taskflags = taskflags
+
+class TaskSucceeded(TaskBase):
+    """Task execution completed"""
+
+class TaskFailed(TaskBase):
+    """Task execution failed"""
+
+    def __init__(self, task, logfile, metadata, errprinted = False):
+        self.errprinted = errprinted
+        super(TaskFailed, self).__init__(task, logfile, metadata)
+
+class TaskFailedSilent(TaskBase):
+    """Task execution failed (silently)"""
+    def getDisplayName(self):
+        # Don't need to tell the user it was silent
+        return "Failed"
+
+class TaskInvalid(TaskBase):
+
+    def __init__(self, task, metadata):
+        super(TaskInvalid, self).__init__(task, None, metadata)
+        self._message = "No such task '%s'" % task
+
+
+class LogTee(object):
+    def __init__(self, logger, outfile):
+        self.outfile = outfile
+        self.logger = logger
+        self.name = self.outfile.name
+
+    def write(self, string):
+        self.logger.plain(string)
+        self.outfile.write(string)
+
+    def __enter__(self):
+        self.outfile.__enter__()
+        return self
+
+    def __exit__(self, *excinfo):
+        self.outfile.__exit__(*excinfo)
+
+    def __repr__(self):
+        return '<LogTee {0}>'.format(self.name)
+    def flush(self):
+        self.outfile.flush()
+
+def exec_func(func, d, dirs = None):
+    """Execute a BB 'function'"""
+
+    body = d.getVar(func, False)
+    if not body:
+        if body is None:
+            logger.warn("Function %s doesn't exist", func)
+        return
+
+    flags = d.getVarFlags(func)
+    cleandirs = flags.get('cleandirs')
+    if cleandirs:
+        for cdir in d.expand(cleandirs).split():
+            bb.utils.remove(cdir, True)
+            bb.utils.mkdirhier(cdir)
+
+    if dirs is None:
+        dirs = flags.get('dirs')
+        if dirs:
+            dirs = d.expand(dirs).split()
+
+    if dirs:
+        for adir in dirs:
+            bb.utils.mkdirhier(adir)
+        adir = dirs[-1]
+    else:
+        adir = d.getVar('B', True)
+        bb.utils.mkdirhier(adir)
+
+    ispython = flags.get('python')
+
+    lockflag = flags.get('lockfiles')
+    if lockflag:
+        lockfiles = [f for f in d.expand(lockflag).split()]
+    else:
+        lockfiles = None
+
+    tempdir = d.getVar('T', True)
+
+    # or func allows items to be executed outside of the normal
+    # task set, such as buildhistory
+    task = d.getVar('BB_RUNTASK', True) or func
+    if task == func:
+        taskfunc = task
+    else:
+        taskfunc = "%s.%s" % (task, func)
+
+    runfmt = d.getVar('BB_RUNFMT', True) or "run.{func}.{pid}"
+    runfn = runfmt.format(taskfunc=taskfunc, task=task, func=func, pid=os.getpid())
+    runfile = os.path.join(tempdir, runfn)
+    bb.utils.mkdirhier(os.path.dirname(runfile))
+
+    # Setup the courtesy link to the runfn, only for tasks
+    # we create the link 'just' before the run script is created
+    # if we create it after, and if the run script fails, then the
+    # link won't be created as an exception would be fired.
+    if task == func:
+        runlink = os.path.join(tempdir, 'run.{0}'.format(task))
+        if runlink:
+            bb.utils.remove(runlink)
+
+            try:
+                os.symlink(runfn, runlink)
+            except OSError:
+                pass
+
+    with bb.utils.fileslocked(lockfiles):
+        if ispython:
+            exec_func_python(func, d, runfile, cwd=adir)
+        else:
+            exec_func_shell(func, d, runfile, cwd=adir)
+
+_functionfmt = """
+def {function}(d):
+{body}
+
+{function}(d)
+"""
+logformatter = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
+def exec_func_python(func, d, runfile, cwd=None):
+    """Execute a python BB 'function'"""
+
+    bbfile = d.getVar('FILE', True)
+    code = _functionfmt.format(function=func, body=d.getVar(func, True))
+    bb.utils.mkdirhier(os.path.dirname(runfile))
+    with open(runfile, 'w') as script:
+        bb.data.emit_func_python(func, script, d)
+
+    if cwd:
+        try:
+            olddir = os.getcwd()
+        except OSError:
+            olddir = None
+        os.chdir(cwd)
+
+    bb.debug(2, "Executing python function %s" % func)
+
+    try:
+        comp = utils.better_compile(code, func, bbfile)
+        utils.better_exec(comp, {"d": d}, code, bbfile)
+    except (bb.parse.SkipRecipe, bb.build.FuncFailed):
+        raise
+    except:
+        raise FuncFailed(func, None)
+    finally:
+        bb.debug(2, "Python function %s finished" % func)
+
+        if cwd and olddir:
+            try:
+                os.chdir(olddir)
+            except OSError:
+                pass
+
+def shell_trap_code():
+    return '''#!/bin/sh\n
+# Emit a useful diagnostic if something fails:
+bb_exit_handler() {
+    ret=$?
+    case $ret in
+    0)  ;;
+    *)  case $BASH_VERSION in
+        "")   echo "WARNING: exit code $ret from a shell command.";;
+        *)    echo "WARNING: ${BASH_SOURCE[0]}:${BASH_LINENO[0]} exit $ret from
+  \"$BASH_COMMAND\"";;
+        esac
+        exit $ret
+    esac
+}
+trap 'bb_exit_handler' 0
+set -e
+'''
+
+def exec_func_shell(func, d, runfile, cwd=None):
+    """Execute a shell function from the metadata
+
+    Note on directory behavior.  The 'dirs' varflag should contain a list
+    of the directories you need created prior to execution.  The last
+    item in the list is where we will chdir/cd to.
+    """
+
+    # Don't let the emitted shell script override PWD
+    d.delVarFlag('PWD', 'export')
+
+    with open(runfile, 'w') as script:
+        script.write(shell_trap_code())
+
+        bb.data.emit_func(func, script, d)
+
+        if bb.msg.loggerVerboseLogs:
+            script.write("set -x\n")
+        if cwd:
+            script.write("cd '%s'\n" % cwd)
+        script.write("%s\n" % func)
+        script.write('''
+# cleanup
+ret=$?
+trap '' 0
+exit $ret
+''')
+
+    os.chmod(runfile, 0775)
+
+    cmd = runfile
+    if d.getVarFlag(func, 'fakeroot'):
+        fakerootcmd = d.getVar('FAKEROOT', True)
+        if fakerootcmd:
+            cmd = [fakerootcmd, runfile]
+
+    if bb.msg.loggerDefaultVerbose:
+        logfile = LogTee(logger, sys.stdout)
+    else:
+        logfile = sys.stdout
+
+    def readfifo(data):
+        lines = data.split('\0')
+        for line in lines:
+            splitval = line.split(' ', 1)
+            cmd = splitval[0]
+            if len(splitval) > 1:
+                value = splitval[1]
+            else:
+                value = ''
+            if cmd == 'bbplain':
+                bb.plain(value)
+            elif cmd == 'bbnote':
+                bb.note(value)
+            elif cmd == 'bbwarn':
+                bb.warn(value)
+            elif cmd == 'bberror':
+                bb.error(value)
+            elif cmd == 'bbfatal':
+                # The caller will call exit themselves, so bb.error() is
+                # what we want here rather than bb.fatal()
+                bb.error(value)
+            elif cmd == 'bbfatal_log':
+                bb.error(value, forcelog=True)
+            elif cmd == 'bbdebug':
+                splitval = value.split(' ', 1)
+                level = int(splitval[0])
+                value = splitval[1]
+                bb.debug(level, value)
+
+    tempdir = d.getVar('T', True)
+    fifopath = os.path.join(tempdir, 'fifo.%s' % os.getpid())
+    if os.path.exists(fifopath):
+        os.unlink(fifopath)
+    os.mkfifo(fifopath)
+    with open(fifopath, 'r+') as fifo:
+        try:
+            bb.debug(2, "Executing shell function %s" % func)
+
+            try:
+                with open(os.devnull, 'r+') as stdin:
+                    bb.process.run(cmd, shell=False, stdin=stdin, log=logfile, extrafiles=[(fifo,readfifo)])
+            except bb.process.CmdError:
+                logfn = d.getVar('BB_LOGFILE', True)
+                raise FuncFailed(func, logfn)
+        finally:
+            os.unlink(fifopath)
+
+    bb.debug(2, "Shell function %s finished" % func)
+
+def _task_data(fn, task, d):
+    localdata = bb.data.createCopy(d)
+    localdata.setVar('BB_FILENAME', fn)
+    localdata.setVar('BB_CURRENTTASK', task[3:])
+    localdata.setVar('OVERRIDES', 'task-%s:%s' %
+                     (task[3:].replace('_', '-'), d.getVar('OVERRIDES', False)))
+    localdata.finalize()
+    bb.data.expandKeys(localdata)
+    return localdata
+
+def _exec_task(fn, task, d, quieterr):
+    """Execute a BB 'task'
+
+    Execution of a task involves a bit more setup than executing a function,
+    running it with its own local metadata, and with some useful variables set.
+    """
+    if not d.getVarFlag(task, 'task'):
+        event.fire(TaskInvalid(task, d), d)
+        logger.error("No such task: %s" % task)
+        return 1
+
+    logger.debug(1, "Executing task %s", task)
+
+    localdata = _task_data(fn, task, d)
+    tempdir = localdata.getVar('T', True)
+    if not tempdir:
+        bb.fatal("T variable not set, unable to build")
+
+    # Change nice level if we're asked to
+    nice = localdata.getVar("BB_TASK_NICE_LEVEL", True)
+    if nice:
+        curnice = os.nice(0)
+        nice = int(nice) - curnice
+        newnice = os.nice(nice)
+        logger.debug(1, "Renice to %s " % newnice)
+
+    bb.utils.mkdirhier(tempdir)
+
+    # Determine the logfile to generate
+    logfmt = localdata.getVar('BB_LOGFMT', True) or 'log.{task}.{pid}'
+    logbase = logfmt.format(task=task, pid=os.getpid())
+
+    # Document the order of the tasks...
+    logorder = os.path.join(tempdir, 'log.task_order')
+    try:
+        with open(logorder, 'a') as logorderfile:
+            logorderfile.write('{0} ({1}): {2}\n'.format(task, os.getpid(), logbase))
+    except OSError:
+        logger.exception("Opening log file '%s'", logorder)
+        pass
+
+    # Setup the courtesy link to the logfn
+    loglink = os.path.join(tempdir, 'log.{0}'.format(task))
+    logfn = os.path.join(tempdir, logbase)
+    if loglink:
+        bb.utils.remove(loglink)
+
+        try:
+           os.symlink(logbase, loglink)
+        except OSError:
+           pass
+
+    prefuncs = localdata.getVarFlag(task, 'prefuncs', expand=True)
+    postfuncs = localdata.getVarFlag(task, 'postfuncs', expand=True)
+
+    class ErrorCheckHandler(logging.Handler):
+        def __init__(self):
+            self.triggered = False
+            logging.Handler.__init__(self, logging.ERROR)
+        def emit(self, record):
+            if getattr(record, 'forcelog', False):
+                self.triggered = False
+            else:
+                self.triggered = True
+
+    # Handle logfiles
+    si = open('/dev/null', 'r')
+    try:
+        bb.utils.mkdirhier(os.path.dirname(logfn))
+        logfile = open(logfn, 'w')
+    except OSError:
+        logger.exception("Opening log file '%s'", logfn)
+        pass
+
+    # Dup the existing fds so we dont lose them
+    osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
+    oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
+    ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
+
+    # Replace those fds with our own
+    os.dup2(si.fileno(), osi[1])
+    os.dup2(logfile.fileno(), oso[1])
+    os.dup2(logfile.fileno(), ose[1])
+
+    # Ensure Python logging goes to the logfile
+    handler = logging.StreamHandler(logfile)
+    handler.setFormatter(logformatter)
+    # Always enable full debug output into task logfiles
+    handler.setLevel(logging.DEBUG - 2)
+    bblogger.addHandler(handler)
+
+    errchk = ErrorCheckHandler()
+    bblogger.addHandler(errchk)
+
+    localdata.setVar('BB_LOGFILE', logfn)
+    localdata.setVar('BB_RUNTASK', task)
+
+    flags = localdata.getVarFlags(task)
+
+    event.fire(TaskStarted(task, logfn, flags, localdata), localdata)
+    try:
+        for func in (prefuncs or '').split():
+            exec_func(func, localdata)
+        exec_func(task, localdata)
+        for func in (postfuncs or '').split():
+            exec_func(func, localdata)
+    except FuncFailed as exc:
+        if quieterr:
+            event.fire(TaskFailedSilent(task, logfn, localdata), localdata)
+        else:
+            errprinted = errchk.triggered
+            logger.error(str(exc))
+            event.fire(TaskFailed(task, logfn, localdata, errprinted), localdata)
+        return 1
+    finally:
+        sys.stdout.flush()
+        sys.stderr.flush()
+
+        bblogger.removeHandler(handler)
+
+        # Restore the backup fds
+        os.dup2(osi[0], osi[1])
+        os.dup2(oso[0], oso[1])
+        os.dup2(ose[0], ose[1])
+
+        # Close the backup fds
+        os.close(osi[0])
+        os.close(oso[0])
+        os.close(ose[0])
+        si.close()
+
+        logfile.close()
+        if os.path.exists(logfn) and os.path.getsize(logfn) == 0:
+            logger.debug(2, "Zero size logfn %s, removing", logfn)
+            bb.utils.remove(logfn)
+            bb.utils.remove(loglink)
+    event.fire(TaskSucceeded(task, logfn, localdata), localdata)
+
+    if not localdata.getVarFlag(task, 'nostamp') and not localdata.getVarFlag(task, 'selfstamp'):
+        make_stamp(task, localdata)
+
+    return 0
+
+def exec_task(fn, task, d, profile = False):
+    try:
+        quieterr = False
+        if d.getVarFlag(task, "quieterrors") is not None:
+            quieterr = True
+
+        if profile:
+            profname = "profile-%s.log" % (d.getVar("PN", True) + "-" + task)
+            try:
+                import cProfile as profile
+            except:
+                import profile
+            prof = profile.Profile()
+            ret = profile.Profile.runcall(prof, _exec_task, fn, task, d, quieterr)
+            prof.dump_stats(profname)
+            bb.utils.process_profilelog(profname)
+
+            return ret
+        else:
+            return _exec_task(fn, task, d, quieterr)
+
+    except Exception:
+        from traceback import format_exc
+        if not quieterr:
+            logger.error("Build of %s failed" % (task))
+            logger.error(format_exc())
+            failedevent = TaskFailed(task, None, d, True)
+            event.fire(failedevent, d)
+        return 1
+
+def stamp_internal(taskname, d, file_name, baseonly=False):
+    """
+    Internal stamp helper function
+    Makes sure the stamp directory exists
+    Returns the stamp path+filename
+
+    In the bitbake core, d can be a CacheData and file_name will be set.
+    When called in task context, d will be a data store, file_name will not be set
+    """
+    taskflagname = taskname
+    if taskname.endswith("_setscene") and taskname != "do_setscene":
+        taskflagname = taskname.replace("_setscene", "")
+
+    if file_name:
+        stamp = d.stamp_base[file_name].get(taskflagname) or d.stamp[file_name]
+        extrainfo = d.stamp_extrainfo[file_name].get(taskflagname) or ""
+    else:
+        stamp = d.getVarFlag(taskflagname, 'stamp-base', True) or d.getVar('STAMP', True)
+        file_name = d.getVar('BB_FILENAME', True)
+        extrainfo = d.getVarFlag(taskflagname, 'stamp-extra-info', True) or ""
+
+    if baseonly:
+        return stamp
+
+    if not stamp:
+        return
+
+    stamp = bb.parse.siggen.stampfile(stamp, file_name, taskname, extrainfo)
+
+    stampdir = os.path.dirname(stamp)
+    if cached_mtime_noerror(stampdir) == 0:
+        bb.utils.mkdirhier(stampdir)
+
+    return stamp
+
+def stamp_cleanmask_internal(taskname, d, file_name):
+    """
+    Internal stamp helper function to generate stamp cleaning mask
+    Returns the stamp path+filename
+
+    In the bitbake core, d can be a CacheData and file_name will be set.
+    When called in task context, d will be a data store, file_name will not be set
+    """
+    taskflagname = taskname
+    if taskname.endswith("_setscene") and taskname != "do_setscene":
+        taskflagname = taskname.replace("_setscene", "")
+
+    if file_name:
+        stamp = d.stamp_base_clean[file_name].get(taskflagname) or d.stampclean[file_name]
+        extrainfo = d.stamp_extrainfo[file_name].get(taskflagname) or ""
+    else:
+        stamp = d.getVarFlag(taskflagname, 'stamp-base-clean', True) or d.getVar('STAMPCLEAN', True)
+        file_name = d.getVar('BB_FILENAME', True)
+        extrainfo = d.getVarFlag(taskflagname, 'stamp-extra-info', True) or ""
+
+    if not stamp:
+        return []
+
+    cleanmask = bb.parse.siggen.stampcleanmask(stamp, file_name, taskname, extrainfo)
+
+    return [cleanmask, cleanmask.replace(taskflagname, taskflagname + "_setscene")]
+
+def make_stamp(task, d, file_name = None):
+    """
+    Creates/updates a stamp for a given task
+    (d can be a data dict or dataCache)
+    """
+    cleanmask = stamp_cleanmask_internal(task, d, file_name)
+    for mask in cleanmask:
+        for name in glob.glob(mask):
+            # Preserve sigdata files in the stamps directory
+            if "sigdata" in name:
+                continue
+            # Preserve taint files in the stamps directory
+            if name.endswith('.taint'):
+                continue
+            os.unlink(name)
+
+    stamp = stamp_internal(task, d, file_name)
+    # Remove the file and recreate to force timestamp
+    # change on broken NFS filesystems
+    if stamp:
+        bb.utils.remove(stamp)
+        open(stamp, "w").close()
+
+    # If we're in task context, write out a signature file for each task
+    # as it completes
+    if not task.endswith("_setscene") and task != "do_setscene" and not file_name:
+        stampbase = stamp_internal(task, d, None, True)
+        file_name = d.getVar('BB_FILENAME', True)
+        bb.parse.siggen.dump_sigtask(file_name, task, stampbase, True)
+
+def del_stamp(task, d, file_name = None):
+    """
+    Removes a stamp for a given task
+    (d can be a data dict or dataCache)
+    """
+    stamp = stamp_internal(task, d, file_name)
+    bb.utils.remove(stamp)
+
+def write_taint(task, d, file_name = None):
+    """
+    Creates a "taint" file which will force the specified task and its
+    dependents to be re-run the next time by influencing the value of its
+    taskhash.
+    (d can be a data dict or dataCache)
+    """
+    import uuid
+    if file_name:
+        taintfn = d.stamp[file_name] + '.' + task + '.taint'
+    else:
+        taintfn = d.getVar('STAMP', True) + '.' + task + '.taint'
+    bb.utils.mkdirhier(os.path.dirname(taintfn))
+    # The specific content of the taint file is not really important,
+    # we just need it to be random, so a random UUID is used
+    with open(taintfn, 'w') as taintf:
+        taintf.write(str(uuid.uuid4()))
+
+def stampfile(taskname, d, file_name = None):
+    """
+    Return the stamp for a given task
+    (d can be a data dict or dataCache)
+    """
+    return stamp_internal(taskname, d, file_name)
+
+def add_tasks(tasklist, d):
+    task_deps = d.getVar('_task_deps', False)
+    if not task_deps:
+        task_deps = {}
+    if not 'tasks' in task_deps:
+        task_deps['tasks'] = []
+    if not 'parents' in task_deps:
+        task_deps['parents'] = {}
+
+    for task in tasklist:
+        task = d.expand(task)
+
+        d.setVarFlag(task, 'task', 1)
+
+        if not task in task_deps['tasks']:
+            task_deps['tasks'].append(task)
+
+        flags = d.getVarFlags(task)
+        def getTask(name):
+            if not name in task_deps:
+                task_deps[name] = {}
+            if name in flags:
+                deptask = d.expand(flags[name])
+                task_deps[name][task] = deptask
+        getTask('depends')
+        getTask('rdepends')
+        getTask('deptask')
+        getTask('rdeptask')
+        getTask('recrdeptask')
+        getTask('recideptask')
+        getTask('nostamp')
+        getTask('fakeroot')
+        getTask('noexec')
+        getTask('umask')
+        task_deps['parents'][task] = []
+        if 'deps' in flags:
+            for dep in flags['deps']:
+                dep = d.expand(dep)
+                task_deps['parents'][task].append(dep)
+
+    # don't assume holding a reference
+    d.setVar('_task_deps', task_deps)
+
+def addtask(task, before, after, d):
+    if task[:3] != "do_":
+        task = "do_" + task
+
+    d.setVarFlag(task, "task", 1)
+    bbtasks = d.getVar('__BBTASKS', False) or []
+    if task not in bbtasks:
+        bbtasks.append(task)
+    d.setVar('__BBTASKS', bbtasks)
+
+    existing = d.getVarFlag(task, "deps") or []
+    if after is not None:
+        # set up deps for function
+        for entry in after.split():
+            if entry not in existing:
+                existing.append(entry)
+    d.setVarFlag(task, "deps", existing)
+    if before is not None:
+        # set up things that depend on this func
+        for entry in before.split():
+            existing = d.getVarFlag(entry, "deps") or []
+            if task not in existing:
+                d.setVarFlag(entry, "deps", [task] + existing)
+
+def deltask(task, d):
+    if task[:3] != "do_":
+        task = "do_" + task
+
+    bbtasks = d.getVar('__BBTASKS', False) or []
+    if task in bbtasks:
+        bbtasks.remove(task)
+        d.setVar('__BBTASKS', bbtasks)
+
+    d.delVarFlag(task, 'deps')
+    for bbtask in d.getVar('__BBTASKS', False) or []:
+        deps = d.getVarFlag(bbtask, 'deps') or []
+        if task in deps:
+            deps.remove(task)
+            d.setVarFlag(bbtask, 'deps', deps)
diff --git a/bitbake/lib/bb/cache.py b/bitbake/lib/bb/cache.py
new file mode 100644
index 0000000..ef4d660
--- /dev/null
+++ b/bitbake/lib/bb/cache.py
@@ -0,0 +1,837 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Cache implementation
+#
+# Caching of bitbake variables before task execution
+
+# Copyright (C) 2006        Richard Purdie
+# Copyright (C) 2012        Intel Corporation
+
+# but small sections based on code from bin/bitbake:
+# Copyright (C) 2003, 2004  Chris Larson
+# Copyright (C) 2003, 2004  Phil Blundell
+# Copyright (C) 2003 - 2005 Michael 'Mickey' Lauer
+# Copyright (C) 2005        Holger Hans Peter Freyther
+# Copyright (C) 2005        ROAD GmbH
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+import os
+import logging
+from collections import defaultdict
+import bb.utils
+
+logger = logging.getLogger("BitBake.Cache")
+
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+    logger.info("Importing cPickle failed. "
+                "Falling back to a very slow implementation.")
+
+__cache_version__ = "148"
+
+def getCacheFile(path, filename, data_hash):
+    return os.path.join(path, filename + "." + data_hash)
+
+# RecipeInfoCommon defines common data retrieving methods
+# from meta data for caches. CoreRecipeInfo as well as other
+# Extra RecipeInfo needs to inherit this class
+class RecipeInfoCommon(object):
+
+    @classmethod
+    def listvar(cls, var, metadata):
+        return cls.getvar(var, metadata).split()
+
+    @classmethod
+    def intvar(cls, var, metadata):
+        return int(cls.getvar(var, metadata) or 0)
+
+    @classmethod
+    def depvar(cls, var, metadata):
+        return bb.utils.explode_deps(cls.getvar(var, metadata))
+
+    @classmethod
+    def pkgvar(cls, var, packages, metadata):
+        return dict((pkg, cls.depvar("%s_%s" % (var, pkg), metadata))
+                    for pkg in packages)
+
+    @classmethod
+    def taskvar(cls, var, tasks, metadata):
+        return dict((task, cls.getvar("%s_task-%s" % (var, task), metadata))
+                    for task in tasks)
+
+    @classmethod
+    def flaglist(cls, flag, varlist, metadata, squash=False):
+        out_dict = dict((var, metadata.getVarFlag(var, flag, True))
+                    for var in varlist)
+        if squash:
+            return dict((k,v) for (k,v) in out_dict.iteritems() if v)
+        else:
+            return out_dict
+
+    @classmethod
+    def getvar(cls, var, metadata):
+        return metadata.getVar(var, True) or ''
+
+
+class CoreRecipeInfo(RecipeInfoCommon):
+    __slots__ = ()
+
+    cachefile = "bb_cache.dat"   
+
+    def __init__(self, filename, metadata):      
+        self.file_depends = metadata.getVar('__depends', False)
+        self.timestamp = bb.parse.cached_mtime(filename)
+        self.variants = self.listvar('__VARIANTS', metadata) + ['']
+        self.appends = self.listvar('__BBAPPEND', metadata)
+        self.nocache = self.getvar('__BB_DONT_CACHE', metadata)
+
+        self.skipreason = self.getvar('__SKIPPED', metadata)
+        if self.skipreason:
+            self.pn = self.getvar('PN', metadata) or bb.parse.BBHandler.vars_from_file(filename,metadata)[0]
+            self.skipped = True
+            self.provides  = self.depvar('PROVIDES', metadata)
+            self.rprovides = self.depvar('RPROVIDES', metadata)
+            return
+
+        self.tasks = metadata.getVar('__BBTASKS', False)
+
+        self.pn = self.getvar('PN', metadata)
+        self.packages = self.listvar('PACKAGES', metadata)
+        if not self.pn in self.packages:
+            self.packages.append(self.pn)
+
+        self.basetaskhashes = self.taskvar('BB_BASEHASH', self.tasks, metadata)
+        self.hashfilename = self.getvar('BB_HASHFILENAME', metadata)
+
+        self.task_deps = metadata.getVar('_task_deps', False) or {'tasks': [], 'parents': {}}
+
+        self.skipped = False
+        self.pe = self.getvar('PE', metadata)
+        self.pv = self.getvar('PV', metadata)
+        self.pr = self.getvar('PR', metadata)
+        self.defaultpref = self.intvar('DEFAULT_PREFERENCE', metadata)
+        self.not_world = self.getvar('EXCLUDE_FROM_WORLD', metadata)
+        self.stamp = self.getvar('STAMP', metadata)
+        self.stampclean = self.getvar('STAMPCLEAN', metadata)        
+        self.stamp_base = self.flaglist('stamp-base', self.tasks, metadata)
+        self.stamp_base_clean = self.flaglist('stamp-base-clean', self.tasks, metadata)
+        self.stamp_extrainfo = self.flaglist('stamp-extra-info', self.tasks, metadata)
+        self.file_checksums = self.flaglist('file-checksums', self.tasks, metadata, True)
+        self.packages_dynamic = self.listvar('PACKAGES_DYNAMIC', metadata)
+        self.depends          = self.depvar('DEPENDS', metadata)
+        self.provides         = self.depvar('PROVIDES', metadata)
+        self.rdepends         = self.depvar('RDEPENDS', metadata)
+        self.rprovides        = self.depvar('RPROVIDES', metadata)
+        self.rrecommends      = self.depvar('RRECOMMENDS', metadata)
+        self.rprovides_pkg    = self.pkgvar('RPROVIDES', self.packages, metadata)
+        self.rdepends_pkg     = self.pkgvar('RDEPENDS', self.packages, metadata)
+        self.rrecommends_pkg  = self.pkgvar('RRECOMMENDS', self.packages, metadata)
+        self.inherits         = self.getvar('__inherit_cache', metadata)
+        self.fakerootenv      = self.getvar('FAKEROOTENV', metadata)
+        self.fakerootdirs     = self.getvar('FAKEROOTDIRS', metadata)
+        self.fakerootnoenv    = self.getvar('FAKEROOTNOENV', metadata)
+
+    @classmethod
+    def init_cacheData(cls, cachedata):
+        # CacheData in Core RecipeInfo Class
+        cachedata.task_deps = {}
+        cachedata.pkg_fn = {}
+        cachedata.pkg_pn = defaultdict(list)
+        cachedata.pkg_pepvpr = {}
+        cachedata.pkg_dp = {}
+
+        cachedata.stamp = {}
+        cachedata.stampclean = {}
+        cachedata.stamp_base = {}
+        cachedata.stamp_base_clean = {}
+        cachedata.stamp_extrainfo = {}
+        cachedata.file_checksums = {}
+        cachedata.fn_provides = {}
+        cachedata.pn_provides = defaultdict(list)
+        cachedata.all_depends = []
+
+        cachedata.deps = defaultdict(list)
+        cachedata.packages = defaultdict(list)
+        cachedata.providers = defaultdict(list)
+        cachedata.rproviders = defaultdict(list)
+        cachedata.packages_dynamic = defaultdict(list)
+
+        cachedata.rundeps = defaultdict(lambda: defaultdict(list))
+        cachedata.runrecs = defaultdict(lambda: defaultdict(list))
+        cachedata.possible_world = []
+        cachedata.universe_target = []
+        cachedata.hashfn = {}
+
+        cachedata.basetaskhash = {}
+        cachedata.inherits = {}
+        cachedata.fakerootenv = {}
+        cachedata.fakerootnoenv = {}
+        cachedata.fakerootdirs = {}
+
+    def add_cacheData(self, cachedata, fn):
+        cachedata.task_deps[fn] = self.task_deps
+        cachedata.pkg_fn[fn] = self.pn
+        cachedata.pkg_pn[self.pn].append(fn)
+        cachedata.pkg_pepvpr[fn] = (self.pe, self.pv, self.pr)
+        cachedata.pkg_dp[fn] = self.defaultpref
+        cachedata.stamp[fn] = self.stamp
+        cachedata.stampclean[fn] = self.stampclean
+        cachedata.stamp_base[fn] = self.stamp_base
+        cachedata.stamp_base_clean[fn] = self.stamp_base_clean
+        cachedata.stamp_extrainfo[fn] = self.stamp_extrainfo
+        cachedata.file_checksums[fn] = self.file_checksums
+
+        provides = [self.pn]
+        for provide in self.provides:
+            if provide not in provides:
+                provides.append(provide)
+        cachedata.fn_provides[fn] = provides
+
+        for provide in provides:
+            cachedata.providers[provide].append(fn)
+            if provide not in cachedata.pn_provides[self.pn]:
+                cachedata.pn_provides[self.pn].append(provide)
+
+        for dep in self.depends:
+            if dep not in cachedata.deps[fn]:
+                cachedata.deps[fn].append(dep)
+            if dep not in cachedata.all_depends:
+                cachedata.all_depends.append(dep)
+
+        rprovides = self.rprovides
+        for package in self.packages:
+            cachedata.packages[package].append(fn)
+            rprovides += self.rprovides_pkg[package]
+
+        for rprovide in rprovides:
+            cachedata.rproviders[rprovide].append(fn)
+
+        for package in self.packages_dynamic:
+            cachedata.packages_dynamic[package].append(fn)
+
+        # Build hash of runtime depends and recommends
+        for package in self.packages + [self.pn]:
+            cachedata.rundeps[fn][package] = list(self.rdepends) + self.rdepends_pkg[package]
+            cachedata.runrecs[fn][package] = list(self.rrecommends) + self.rrecommends_pkg[package]
+
+        # Collect files we may need for possible world-dep
+        # calculations
+        if self.not_world:
+            logger.debug(1, "EXCLUDE FROM WORLD: %s", fn)
+        else:
+            cachedata.possible_world.append(fn)
+
+        # create a collection of all targets for sanity checking
+        # tasks, such as upstream versions, license, and tools for
+        # task and image creation.
+        cachedata.universe_target.append(self.pn)
+
+        cachedata.hashfn[fn] = self.hashfilename
+        for task, taskhash in self.basetaskhashes.iteritems():
+            identifier = '%s.%s' % (fn, task)
+            cachedata.basetaskhash[identifier] = taskhash
+
+        cachedata.inherits[fn] = self.inherits
+        cachedata.fakerootenv[fn] = self.fakerootenv
+        cachedata.fakerootnoenv[fn] = self.fakerootnoenv
+        cachedata.fakerootdirs[fn] = self.fakerootdirs
+
+
+
+class Cache(object):
+    """
+    BitBake Cache implementation
+    """
+
+    def __init__(self, data, data_hash, caches_array):
+        # Pass caches_array information into Cache Constructor
+        # It will be used later for deciding whether we 
+        # need extra cache file dump/load support 
+        self.caches_array = caches_array
+        self.cachedir = data.getVar("CACHE", True)
+        self.clean = set()
+        self.checked = set()
+        self.depends_cache = {}
+        self.data = None
+        self.data_fn = None
+        self.cacheclean = True
+        self.data_hash = data_hash
+
+        if self.cachedir in [None, '']:
+            self.has_cache = False
+            logger.info("Not using a cache. "
+                        "Set CACHE = <directory> to enable.")
+            return
+
+        self.has_cache = True
+        self.cachefile = getCacheFile(self.cachedir, "bb_cache.dat", self.data_hash)
+
+        logger.debug(1, "Using cache in '%s'", self.cachedir)
+        bb.utils.mkdirhier(self.cachedir)
+
+        cache_ok = True
+        if self.caches_array:
+            for cache_class in self.caches_array:
+                if type(cache_class) is type and issubclass(cache_class, RecipeInfoCommon):
+                    cachefile = getCacheFile(self.cachedir, cache_class.cachefile, self.data_hash)
+                    cache_ok = cache_ok and os.path.exists(cachefile)
+                    cache_class.init_cacheData(self)
+        if cache_ok:
+            self.load_cachefile()
+        elif os.path.isfile(self.cachefile):
+            logger.info("Out of date cache found, rebuilding...")
+
+    def load_cachefile(self):
+        # Firstly, using core cache file information for
+        # valid checking
+        with open(self.cachefile, "rb") as cachefile:
+            pickled = pickle.Unpickler(cachefile)
+            try:
+                cache_ver = pickled.load()
+                bitbake_ver = pickled.load()
+            except Exception:
+                logger.info('Invalid cache, rebuilding...')
+                return
+
+            if cache_ver != __cache_version__:
+                logger.info('Cache version mismatch, rebuilding...')
+                return
+            elif bitbake_ver != bb.__version__:
+                logger.info('Bitbake version mismatch, rebuilding...')
+                return
+
+
+        cachesize = 0
+        previous_progress = 0
+        previous_percent = 0
+
+        # Calculate the correct cachesize of all those cache files
+        for cache_class in self.caches_array:
+            if type(cache_class) is type and issubclass(cache_class, RecipeInfoCommon):
+                cachefile = getCacheFile(self.cachedir, cache_class.cachefile, self.data_hash)
+                with open(cachefile, "rb") as cachefile:
+                    cachesize += os.fstat(cachefile.fileno()).st_size
+
+        bb.event.fire(bb.event.CacheLoadStarted(cachesize), self.data)
+        
+        for cache_class in self.caches_array:
+            if type(cache_class) is type and issubclass(cache_class, RecipeInfoCommon):
+                cachefile = getCacheFile(self.cachedir, cache_class.cachefile, self.data_hash)
+                with open(cachefile, "rb") as cachefile:
+                    pickled = pickle.Unpickler(cachefile)                    
+                    while cachefile:
+                        try:
+                            key = pickled.load()
+                            value = pickled.load()
+                        except Exception:
+                            break
+                        if self.depends_cache.has_key(key):
+                            self.depends_cache[key].append(value)
+                        else:
+                            self.depends_cache[key] = [value]
+                        # only fire events on even percentage boundaries
+                        current_progress = cachefile.tell() + previous_progress
+                        current_percent = 100 * current_progress / cachesize
+                        if current_percent > previous_percent:
+                            previous_percent = current_percent
+                            bb.event.fire(bb.event.CacheLoadProgress(current_progress, cachesize),
+                                          self.data)
+
+                    previous_progress += current_progress
+
+        # Note: depends cache number is corresponding to the parsing file numbers.
+        # The same file has several caches, still regarded as one item in the cache
+        bb.event.fire(bb.event.CacheLoadCompleted(cachesize,
+                                                  len(self.depends_cache)),
+                      self.data)
+
+    
+    @staticmethod
+    def virtualfn2realfn(virtualfn):
+        """
+        Convert a virtual file name to a real one + the associated subclass keyword
+        """
+
+        fn = virtualfn
+        cls = ""
+        if virtualfn.startswith('virtual:'):
+            elems = virtualfn.split(':')
+            cls = ":".join(elems[1:-1])
+            fn = elems[-1]
+        return (fn, cls)
+
+    @staticmethod
+    def realfn2virtual(realfn, cls):
+        """
+        Convert a real filename + the associated subclass keyword to a virtual filename
+        """
+        if cls == "":
+            return realfn
+        return "virtual:" + cls + ":" + realfn
+
+    @classmethod
+    def loadDataFull(cls, virtualfn, appends, cfgData):
+        """
+        Return a complete set of data for fn.
+        To do this, we need to parse the file.
+        """
+
+        (fn, virtual) = cls.virtualfn2realfn(virtualfn)
+
+        logger.debug(1, "Parsing %s (full)", fn)
+
+        cfgData.setVar("__ONLYFINALISE", virtual or "default")
+        bb_data = cls.load_bbfile(fn, appends, cfgData)
+        return bb_data[virtual]
+
+    @classmethod
+    def parse(cls, filename, appends, configdata, caches_array):
+        """Parse the specified filename, returning the recipe information"""
+        infos = []
+        datastores = cls.load_bbfile(filename, appends, configdata)
+        depends = []
+        for variant, data in sorted(datastores.iteritems(),
+                                    key=lambda i: i[0],
+                                    reverse=True):
+            virtualfn = cls.realfn2virtual(filename, variant)
+            depends = depends + (data.getVar("__depends", False) or [])
+            if depends and not variant:
+                data.setVar("__depends", depends)
+
+            info_array = []
+            for cache_class in caches_array:
+                if type(cache_class) is type and issubclass(cache_class, RecipeInfoCommon):
+                    info = cache_class(filename, data)
+                    info_array.append(info)
+            infos.append((virtualfn, info_array))
+
+        return infos
+
+    def load(self, filename, appends, configdata):
+        """Obtain the recipe information for the specified filename,
+        using cached values if available, otherwise parsing.
+
+        Note that if it does parse to obtain the info, it will not
+        automatically add the information to the cache or to your
+        CacheData.  Use the add or add_info method to do so after
+        running this, or use loadData instead."""
+        cached = self.cacheValid(filename, appends)
+        if cached:
+            infos = []
+            # info_array item is a list of [CoreRecipeInfo, XXXRecipeInfo]
+            info_array = self.depends_cache[filename]
+            for variant in info_array[0].variants:
+                virtualfn = self.realfn2virtual(filename, variant)
+                infos.append((virtualfn, self.depends_cache[virtualfn]))
+        else:
+            logger.debug(1, "Parsing %s", filename)
+            return self.parse(filename, appends, configdata, self.caches_array)
+
+        return cached, infos
+
+    def loadData(self, fn, appends, cfgData, cacheData):
+        """Load the recipe info for the specified filename,
+        parsing and adding to the cache if necessary, and adding
+        the recipe information to the supplied CacheData instance."""
+        skipped, virtuals = 0, 0
+
+        cached, infos = self.load(fn, appends, cfgData)
+        for virtualfn, info_array in infos:
+            if info_array[0].skipped:
+                logger.debug(1, "Skipping %s: %s", virtualfn, info_array[0].skipreason)
+                skipped += 1
+            else:
+                self.add_info(virtualfn, info_array, cacheData, not cached)
+                virtuals += 1
+
+        return cached, skipped, virtuals
+
+    def cacheValid(self, fn, appends):
+        """
+        Is the cache valid for fn?
+        Fast version, no timestamps checked.
+        """
+        if fn not in self.checked:
+            self.cacheValidUpdate(fn, appends)
+
+        # Is cache enabled?
+        if not self.has_cache:
+            return False
+        if fn in self.clean:
+            return True
+        return False
+
+    def cacheValidUpdate(self, fn, appends):
+        """
+        Is the cache valid for fn?
+        Make thorough (slower) checks including timestamps.
+        """
+        # Is cache enabled?
+        if not self.has_cache:
+            return False
+
+        self.checked.add(fn)
+
+        # File isn't in depends_cache
+        if not fn in self.depends_cache:
+            logger.debug(2, "Cache: %s is not cached", fn)
+            return False
+
+        mtime = bb.parse.cached_mtime_noerror(fn)
+
+        # Check file still exists
+        if mtime == 0:
+            logger.debug(2, "Cache: %s no longer exists", fn)
+            self.remove(fn)
+            return False
+
+        info_array = self.depends_cache[fn]
+        # Check the file's timestamp
+        if mtime != info_array[0].timestamp:
+            logger.debug(2, "Cache: %s changed", fn)
+            self.remove(fn)
+            return False
+
+        # Check dependencies are still valid
+        depends = info_array[0].file_depends
+        if depends:
+            for f, old_mtime in depends:
+                fmtime = bb.parse.cached_mtime_noerror(f)
+                # Check if file still exists
+                if old_mtime != 0 and fmtime == 0:
+                    logger.debug(2, "Cache: %s's dependency %s was removed",
+                                    fn, f)
+                    self.remove(fn)
+                    return False
+
+                if (fmtime != old_mtime):
+                    logger.debug(2, "Cache: %s's dependency %s changed",
+                                    fn, f)
+                    self.remove(fn)
+                    return False
+
+        if hasattr(info_array[0], 'file_checksums'):
+            for _, fl in info_array[0].file_checksums.items():
+                for f in fl.split():
+                    if "*" in f:
+                        continue
+                    f, exist = f.split(":")
+                    if (exist == "True" and not os.path.exists(f)) or (exist == "False" and os.path.exists(f)):
+                        logger.debug(2, "Cache: %s's file checksum list file %s changed",
+                                        fn, f)
+                        self.remove(fn)
+                        return False
+
+        if appends != info_array[0].appends:
+            logger.debug(2, "Cache: appends for %s changed", fn)
+            logger.debug(2, "%s to %s" % (str(appends), str(info_array[0].appends)))
+            self.remove(fn)
+            return False
+
+        invalid = False
+        for cls in info_array[0].variants:
+            virtualfn = self.realfn2virtual(fn, cls)
+            self.clean.add(virtualfn)
+            if virtualfn not in self.depends_cache:
+                logger.debug(2, "Cache: %s is not cached", virtualfn)
+                invalid = True
+
+        # If any one of the variants is not present, mark as invalid for all
+        if invalid:
+            for cls in info_array[0].variants:
+                virtualfn = self.realfn2virtual(fn, cls)
+                if virtualfn in self.clean:
+                    logger.debug(2, "Cache: Removing %s from cache", virtualfn)
+                    self.clean.remove(virtualfn)
+            if fn in self.clean:
+                logger.debug(2, "Cache: Marking %s as not clean", fn)
+                self.clean.remove(fn)
+            return False
+
+        self.clean.add(fn)
+        return True
+
+    def remove(self, fn):
+        """
+        Remove a fn from the cache
+        Called from the parser in error cases
+        """
+        if fn in self.depends_cache:
+            logger.debug(1, "Removing %s from cache", fn)
+            del self.depends_cache[fn]
+        if fn in self.clean:
+            logger.debug(1, "Marking %s as unclean", fn)
+            self.clean.remove(fn)
+
+    def sync(self):
+        """
+        Save the cache
+        Called from the parser when complete (or exiting)
+        """
+
+        if not self.has_cache:
+            return
+
+        if self.cacheclean:
+            logger.debug(2, "Cache is clean, not saving.")
+            return
+
+        file_dict = {}
+        pickler_dict = {}
+        for cache_class in self.caches_array:
+            if type(cache_class) is type and issubclass(cache_class, RecipeInfoCommon):
+                cache_class_name = cache_class.__name__
+                cachefile = getCacheFile(self.cachedir, cache_class.cachefile, self.data_hash)
+                file_dict[cache_class_name] = open(cachefile, "wb")
+                pickler_dict[cache_class_name] =  pickle.Pickler(file_dict[cache_class_name], pickle.HIGHEST_PROTOCOL)
+                   
+        pickler_dict['CoreRecipeInfo'].dump(__cache_version__)
+        pickler_dict['CoreRecipeInfo'].dump(bb.__version__)
+
+        try:
+            for key, info_array in self.depends_cache.iteritems():
+                for info in info_array:
+                    if isinstance(info, RecipeInfoCommon):
+                        cache_class_name = info.__class__.__name__
+                        pickler_dict[cache_class_name].dump(key)
+                        pickler_dict[cache_class_name].dump(info)
+        finally:
+            for cache_class in self.caches_array:
+                if type(cache_class) is type and issubclass(cache_class, RecipeInfoCommon):
+                    cache_class_name = cache_class.__name__
+                    file_dict[cache_class_name].close()
+
+        del self.depends_cache
+
+    @staticmethod
+    def mtime(cachefile):
+        return bb.parse.cached_mtime_noerror(cachefile)
+
+    def add_info(self, filename, info_array, cacheData, parsed=None, watcher=None):
+        if isinstance(info_array[0], CoreRecipeInfo) and (not info_array[0].skipped):
+            cacheData.add_from_recipeinfo(filename, info_array)
+
+            if watcher:
+                watcher(info_array[0].file_depends)
+
+        if not self.has_cache:
+            return
+
+        if (info_array[0].skipped or 'SRCREVINACTION' not in info_array[0].pv) and not info_array[0].nocache:
+            if parsed:
+                self.cacheclean = False
+            self.depends_cache[filename] = info_array
+
+    def add(self, file_name, data, cacheData, parsed=None):
+        """
+        Save data we need into the cache
+        """
+
+        realfn = self.virtualfn2realfn(file_name)[0]
+
+        info_array = []
+        for cache_class in self.caches_array:
+            if type(cache_class) is type and issubclass(cache_class, RecipeInfoCommon):
+                info_array.append(cache_class(realfn, data))
+        self.add_info(file_name, info_array, cacheData, parsed)
+
+    @staticmethod
+    def load_bbfile(bbfile, appends, config):
+        """
+        Load and parse one .bb build file
+        Return the data and whether parsing resulted in the file being skipped
+        """
+        chdir_back = False
+
+        from bb import parse
+
+        # expand tmpdir to include this topdir
+        config.setVar('TMPDIR', config.getVar('TMPDIR', True) or "")
+        bbfile_loc = os.path.abspath(os.path.dirname(bbfile))
+        oldpath = os.path.abspath(os.getcwd())
+        parse.cached_mtime_noerror(bbfile_loc)
+        bb_data = config.createCopy()
+        # The ConfHandler first looks if there is a TOPDIR and if not
+        # then it would call getcwd().
+        # Previously, we chdir()ed to bbfile_loc, called the handler
+        # and finally chdir()ed back, a couple of thousand times. We now
+        # just fill in TOPDIR to point to bbfile_loc if there is no TOPDIR yet.
+        if not bb_data.getVar('TOPDIR', False):
+            chdir_back = True
+            bb_data.setVar('TOPDIR', bbfile_loc)
+        try:
+            if appends:
+                bb_data.setVar('__BBAPPEND', " ".join(appends))
+            bb_data = parse.handle(bbfile, bb_data)
+            if chdir_back:
+                os.chdir(oldpath)
+            return bb_data
+        except:
+            if chdir_back:
+                os.chdir(oldpath)
+            raise
+
+
+def init(cooker):
+    """
+    The Objective: Cache the minimum amount of data possible yet get to the
+    stage of building packages (i.e. tryBuild) without reparsing any .bb files.
+
+    To do this, we intercept getVar calls and only cache the variables we see
+    being accessed. We rely on the cache getVar calls being made for all
+    variables bitbake might need to use to reach this stage. For each cached
+    file we need to track:
+
+    * Its mtime
+    * The mtimes of all its dependencies
+    * Whether it caused a parse.SkipRecipe exception
+
+    Files causing parsing errors are evicted from the cache.
+
+    """
+    return Cache(cooker.configuration.data, cooker.configuration.data_hash)
+
+
+class CacheData(object):
+    """
+    The data structures we compile from the cached data
+    """
+
+    def __init__(self, caches_array):
+        self.caches_array = caches_array
+        for cache_class in self.caches_array:
+            if type(cache_class) is type and issubclass(cache_class, RecipeInfoCommon):
+                cache_class.init_cacheData(self)        
+
+        # Direct cache variables
+        self.task_queues = {}
+        self.preferred = {}
+        self.tasks = {}
+        # Indirect Cache variables (set elsewhere)
+        self.ignored_dependencies = []
+        self.world_target = set()
+        self.bbfile_priority = {}
+
+    def add_from_recipeinfo(self, fn, info_array):
+        for info in info_array:
+            info.add_cacheData(self, fn)
+
+class MultiProcessCache(object):
+    """
+    BitBake multi-process cache implementation
+
+    Used by the codeparser & file checksum caches
+    """
+
+    def __init__(self):
+        self.cachefile = None
+        self.cachedata = self.create_cachedata()
+        self.cachedata_extras = self.create_cachedata()
+
+    def init_cache(self, d):
+        cachedir = (d.getVar("PERSISTENT_DIR", True) or
+                    d.getVar("CACHE", True))
+        if cachedir in [None, '']:
+            return
+        bb.utils.mkdirhier(cachedir)
+        self.cachefile = os.path.join(cachedir, self.__class__.cache_file_name)
+        logger.debug(1, "Using cache in '%s'", self.cachefile)
+
+        glf = bb.utils.lockfile(self.cachefile + ".lock")
+
+        try:
+            with open(self.cachefile, "rb") as f:
+                p = pickle.Unpickler(f)
+                data, version = p.load()
+        except:
+            bb.utils.unlockfile(glf)
+            return
+
+        bb.utils.unlockfile(glf)
+
+        if version != self.__class__.CACHE_VERSION:
+            return
+
+        self.cachedata = data
+
+    def create_cachedata(self):
+        data = [{}]
+        return data
+
+    def save_extras(self, d):
+        if not self.cachefile:
+            return
+
+        glf = bb.utils.lockfile(self.cachefile + ".lock", shared=True)
+
+        i = os.getpid()
+        lf = None
+        while not lf:
+            lf = bb.utils.lockfile(self.cachefile + ".lock." + str(i), retry=False)
+            if not lf or os.path.exists(self.cachefile + "-" + str(i)):
+                if lf:
+                    bb.utils.unlockfile(lf)
+                    lf = None
+                i = i + 1
+                continue
+
+            with open(self.cachefile + "-" + str(i), "wb") as f:
+                p = pickle.Pickler(f, -1)
+                p.dump([self.cachedata_extras, self.__class__.CACHE_VERSION])
+
+        bb.utils.unlockfile(lf)
+        bb.utils.unlockfile(glf)
+
+    def merge_data(self, source, dest):
+        for j in range(0,len(dest)):
+            for h in source[j]:
+                if h not in dest[j]:
+                    dest[j][h] = source[j][h]
+
+    def save_merge(self, d):
+        if not self.cachefile:
+            return
+
+        glf = bb.utils.lockfile(self.cachefile + ".lock")
+
+        data = self.cachedata
+
+        for f in [y for y in os.listdir(os.path.dirname(self.cachefile)) if y.startswith(os.path.basename(self.cachefile) + '-')]:
+            f = os.path.join(os.path.dirname(self.cachefile), f)
+            try:
+                with open(f, "rb") as fd:
+                    p = pickle.Unpickler(fd)
+                    extradata, version = p.load()
+            except (IOError, EOFError):
+                os.unlink(f)
+                continue
+
+            if version != self.__class__.CACHE_VERSION:
+                os.unlink(f)
+                continue
+
+            self.merge_data(extradata, data)
+            os.unlink(f)
+
+        with open(self.cachefile, "wb") as f:
+            p = pickle.Pickler(f, -1)
+            p.dump([data, self.__class__.CACHE_VERSION])
+
+        bb.utils.unlockfile(glf)
+
diff --git a/bitbake/lib/bb/cache_extra.py b/bitbake/lib/bb/cache_extra.py
new file mode 100644
index 0000000..83f4959
--- /dev/null
+++ b/bitbake/lib/bb/cache_extra.py
@@ -0,0 +1,75 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Extra RecipeInfo will be all defined in this file. Currently,
+# Only Hob (Image Creator) Requests some extra fields. So
+# HobRecipeInfo is defined. It's named HobRecipeInfo because it
+# is introduced by 'hob'. Users could also introduce other
+# RecipeInfo or simply use those already defined RecipeInfo.
+# In the following patch, this newly defined new extra RecipeInfo
+# will be dynamically loaded and used for loading/saving the extra
+# cache fields  
+
+# Copyright (C) 2011, Intel Corporation. All rights reserved.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from bb.cache import RecipeInfoCommon
+
+class HobRecipeInfo(RecipeInfoCommon):
+    __slots__ = ()
+
+    classname = "HobRecipeInfo"
+    # please override this member with the correct data cache file
+    # such as (bb_cache.dat, bb_extracache_hob.dat) 
+    cachefile = "bb_extracache_" + classname +".dat"        
+
+    # override this member with the list of extra cache fields
+    # that this class will provide
+    cachefields = ['summary', 'license', 'section',
+            'description', 'homepage', 'bugtracker',
+            'prevision', 'files_info']
+
+    def __init__(self, filename, metadata):
+
+        self.summary = self.getvar('SUMMARY', metadata)
+        self.license = self.getvar('LICENSE', metadata)
+        self.section = self.getvar('SECTION', metadata)
+        self.description = self.getvar('DESCRIPTION', metadata)
+        self.homepage = self.getvar('HOMEPAGE', metadata)
+        self.bugtracker = self.getvar('BUGTRACKER', metadata)
+        self.prevision = self.getvar('PR', metadata)
+        self.files_info = self.getvar('FILES_INFO', metadata)
+
+    @classmethod
+    def init_cacheData(cls, cachedata):
+        # CacheData in Hob RecipeInfo Class
+        cachedata.summary = {}
+        cachedata.license = {}
+        cachedata.section = {}
+        cachedata.description = {}
+        cachedata.homepage = {}
+        cachedata.bugtracker = {}
+        cachedata.prevision = {}
+        cachedata.files_info = {}
+
+    def add_cacheData(self, cachedata, fn):
+        cachedata.summary[fn] = self.summary
+        cachedata.license[fn] = self.license
+        cachedata.section[fn] = self.section
+        cachedata.description[fn] = self.description
+        cachedata.homepage[fn] = self.homepage
+        cachedata.bugtracker[fn] = self.bugtracker
+        cachedata.prevision[fn] = self.prevision
+        cachedata.files_info[fn] = self.files_info
diff --git a/bitbake/lib/bb/checksum.py b/bitbake/lib/bb/checksum.py
new file mode 100644
index 0000000..514ff0b
--- /dev/null
+++ b/bitbake/lib/bb/checksum.py
@@ -0,0 +1,90 @@
+# Local file checksum cache implementation
+#
+# Copyright (C) 2012 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import os
+import stat
+import bb.utils
+import logging
+from bb.cache import MultiProcessCache
+
+logger = logging.getLogger("BitBake.Cache")
+
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+    logger.info("Importing cPickle failed. "
+                "Falling back to a very slow implementation.")
+
+
+# mtime cache (non-persistent)
+# based upon the assumption that files do not change during bitbake run
+class FileMtimeCache(object):
+    cache = {}
+
+    def cached_mtime(self, f):
+        if f not in self.cache:
+            self.cache[f] = os.stat(f)[stat.ST_MTIME]
+        return self.cache[f]
+
+    def cached_mtime_noerror(self, f):
+        if f not in self.cache:
+            try:
+                self.cache[f] = os.stat(f)[stat.ST_MTIME]
+            except OSError:
+                return 0
+        return self.cache[f]
+
+    def update_mtime(self, f):
+        self.cache[f] = os.stat(f)[stat.ST_MTIME]
+        return self.cache[f]
+
+    def clear(self):
+        self.cache.clear()
+
+# Checksum + mtime cache (persistent)
+class FileChecksumCache(MultiProcessCache):
+    cache_file_name = "local_file_checksum_cache.dat"
+    CACHE_VERSION = 1
+
+    def __init__(self):
+        self.mtime_cache = FileMtimeCache()
+        MultiProcessCache.__init__(self)
+
+    def get_checksum(self, f):
+        entry = self.cachedata[0].get(f)
+        cmtime = self.mtime_cache.cached_mtime(f)
+        if entry:
+            (mtime, hashval) = entry
+            if cmtime == mtime:
+                return hashval
+            else:
+                bb.debug(2, "file %s changed mtime, recompute checksum" % f)
+
+        hashval = bb.utils.md5_file(f)
+        self.cachedata_extras[0][f] = (cmtime, hashval)
+        return hashval
+
+    def merge_data(self, source, dest):
+        for h in source[0]:
+            if h in dest:
+                (smtime, _) = source[0][h]
+                (dmtime, _) = dest[0][h]
+                if smtime > dmtime:
+                    dest[0][h] = source[0][h]
+            else:
+                dest[0][h] = source[0][h]
diff --git a/bitbake/lib/bb/codeparser.py b/bitbake/lib/bb/codeparser.py
new file mode 100644
index 0000000..82a3af4
--- /dev/null
+++ b/bitbake/lib/bb/codeparser.py
@@ -0,0 +1,414 @@
+import ast
+import codegen
+import logging
+import os.path
+import bb.utils, bb.data
+from itertools import chain
+from pysh import pyshyacc, pyshlex, sherrors
+from bb.cache import MultiProcessCache
+
+
+logger = logging.getLogger('BitBake.CodeParser')
+
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+    logger.info('Importing cPickle failed.  Falling back to a very slow implementation.')
+
+
+def check_indent(codestr):
+    """If the code is indented, add a top level piece of code to 'remove' the indentation"""
+
+    i = 0
+    while codestr[i] in ["\n", "\t", " "]:
+        i = i + 1
+
+    if i == 0:
+        return codestr
+
+    if codestr[i-1] == "\t" or codestr[i-1] == " ":
+        return "if 1:\n" + codestr
+
+    return codestr
+
+
+# Basically pickle, in python 2.7.3 at least, does badly with data duplication 
+# upon pickling and unpickling. Combine this with duplicate objects and things
+# are a mess.
+#
+# When the sets are originally created, python calls intern() on the set keys
+# which significantly improves memory usage. Sadly the pickle/unpickle process
+# doesn't call intern() on the keys and results in the same strings being duplicated
+# in memory. This also means pickle will save the same string multiple times in
+# the cache file.
+#
+# By having shell and python cacheline objects with setstate/getstate, we force
+# the object creation through our own routine where we can call intern (via internSet).
+#
+# We also use hashable frozensets and ensure we use references to these so that
+# duplicates can be removed, both in memory and in the resulting pickled data.
+#
+# By playing these games, the size of the cache file shrinks dramatically
+# meaning faster load times and the reloaded cache files also consume much less
+# memory. Smaller cache files, faster load times and lower memory usage is good.
+#
+# A custom getstate/setstate using tuples is actually worth 15% cachesize by
+# avoiding duplication of the attribute names!
+
+class SetCache(object):
+    def __init__(self):
+        self.setcache = {}
+
+    def internSet(self, items):
+        
+        new = []
+        for i in items:
+            new.append(intern(i))
+        s = frozenset(new)
+        if hash(s) in self.setcache:
+            return self.setcache[hash(s)]
+        self.setcache[hash(s)] = s
+        return s
+
+codecache = SetCache()
+
+class pythonCacheLine(object):
+    def __init__(self, refs, execs, contains):
+        self.refs = codecache.internSet(refs)
+        self.execs = codecache.internSet(execs)
+        self.contains = {}
+        for c in contains:
+            self.contains[c] = codecache.internSet(contains[c])
+
+    def __getstate__(self):
+        return (self.refs, self.execs, self.contains)
+
+    def __setstate__(self, state):
+        (refs, execs, contains) = state
+        self.__init__(refs, execs, contains)
+    def __hash__(self):
+        l = (hash(self.refs), hash(self.execs))
+        for c in sorted(self.contains.keys()):
+            l = l + (c, hash(self.contains[c]))
+        return hash(l)
+    def __repr__(self):
+        return " ".join([str(self.refs), str(self.execs), str(self.contains)]) 
+
+
+class shellCacheLine(object):
+    def __init__(self, execs):
+        self.execs = codecache.internSet(execs)
+
+    def __getstate__(self):
+        return (self.execs)
+
+    def __setstate__(self, state):
+        (execs) = state
+        self.__init__(execs)
+    def __hash__(self):
+        return hash(self.execs)
+    def __repr__(self):
+        return str(self.execs)
+
+class CodeParserCache(MultiProcessCache):
+    cache_file_name = "bb_codeparser.dat"
+    CACHE_VERSION = 7
+
+    def __init__(self):
+        MultiProcessCache.__init__(self)
+        self.pythoncache = self.cachedata[0]
+        self.shellcache = self.cachedata[1]
+        self.pythoncacheextras = self.cachedata_extras[0]
+        self.shellcacheextras = self.cachedata_extras[1]
+
+        # To avoid duplication in the codeparser cache, keep
+        # a lookup of hashes of objects we already have
+        self.pythoncachelines = {}
+        self.shellcachelines = {}
+
+    def newPythonCacheLine(self, refs, execs, contains):
+        cacheline = pythonCacheLine(refs, execs, contains)
+        h = hash(cacheline)
+        if h in self.pythoncachelines:
+            return self.pythoncachelines[h]
+        self.pythoncachelines[h] = cacheline
+        return cacheline
+
+    def newShellCacheLine(self, execs):
+        cacheline = shellCacheLine(execs)
+        h = hash(cacheline)
+        if h in self.shellcachelines:
+            return self.shellcachelines[h]
+        self.shellcachelines[h] = cacheline
+        return cacheline
+
+    def init_cache(self, d):
+        MultiProcessCache.init_cache(self, d)
+
+        # cachedata gets re-assigned in the parent
+        self.pythoncache = self.cachedata[0]
+        self.shellcache = self.cachedata[1]
+
+    def create_cachedata(self):
+        data = [{}, {}]
+        return data
+
+codeparsercache = CodeParserCache()
+
+def parser_cache_init(d):
+    codeparsercache.init_cache(d)
+
+def parser_cache_save(d):
+    codeparsercache.save_extras(d)
+
+def parser_cache_savemerge(d):
+    codeparsercache.save_merge(d)
+
+Logger = logging.getLoggerClass()
+class BufferedLogger(Logger):
+    def __init__(self, name, level=0, target=None):
+        Logger.__init__(self, name)
+        self.setLevel(level)
+        self.buffer = []
+        self.target = target
+
+    def handle(self, record):
+        self.buffer.append(record)
+
+    def flush(self):
+        for record in self.buffer:
+            self.target.handle(record)
+        self.buffer = []
+
+class PythonParser():
+    getvars = (".getVar", ".appendVar", ".prependVar")
+    containsfuncs = ("bb.utils.contains", "base_contains", "bb.utils.contains_any")
+    execfuncs = ("bb.build.exec_func", "bb.build.exec_task")
+
+    def warn(self, func, arg):
+        """Warn about calls of bitbake APIs which pass a non-literal
+        argument for the variable name, as we're not able to track such
+        a reference.
+        """
+
+        try:
+            funcstr = codegen.to_source(func)
+            argstr = codegen.to_source(arg)
+        except TypeError:
+            self.log.debug(2, 'Failed to convert function and argument to source form')
+        else:
+            self.log.debug(1, self.unhandled_message % (funcstr, argstr))
+
+    def visit_Call(self, node):
+        name = self.called_node_name(node.func)
+        if name and name.endswith(self.getvars) or name in self.containsfuncs:
+            if isinstance(node.args[0], ast.Str):
+                varname = node.args[0].s
+                if name in self.containsfuncs and isinstance(node.args[1], ast.Str):
+                    if varname not in self.contains:
+                        self.contains[varname] = set()
+                    self.contains[varname].add(node.args[1].s)
+                else:                      
+                    self.references.add(node.args[0].s)
+            else:
+                self.warn(node.func, node.args[0])
+        elif name in self.execfuncs:
+            if isinstance(node.args[0], ast.Str):
+                self.var_execs.add(node.args[0].s)
+            else:
+                self.warn(node.func, node.args[0])
+        elif name and isinstance(node.func, (ast.Name, ast.Attribute)):
+            self.execs.add(name)
+
+    def called_node_name(self, node):
+        """Given a called node, return its original string form"""
+        components = []
+        while node:
+            if isinstance(node, ast.Attribute):
+                components.append(node.attr)
+                node = node.value
+            elif isinstance(node, ast.Name):
+                components.append(node.id)
+                return '.'.join(reversed(components))
+            else:
+                break
+
+    def __init__(self, name, log):
+        self.var_execs = set()
+        self.contains = {}
+        self.execs = set()
+        self.references = set()
+        self.log = BufferedLogger('BitBake.Data.PythonParser', logging.DEBUG, log)
+
+        self.unhandled_message = "in call of %s, argument '%s' is not a string literal"
+        self.unhandled_message = "while parsing %s, %s" % (name, self.unhandled_message)
+
+    def parse_python(self, node):
+        if not node or not node.strip():
+            return
+
+        h = hash(str(node))
+
+        if h in codeparsercache.pythoncache:
+            self.references = set(codeparsercache.pythoncache[h].refs)
+            self.execs = set(codeparsercache.pythoncache[h].execs)
+            self.contains = {}
+            for i in codeparsercache.pythoncache[h].contains:
+                self.contains[i] = set(codeparsercache.pythoncache[h].contains[i])
+            return
+
+        if h in codeparsercache.pythoncacheextras:
+            self.references = set(codeparsercache.pythoncacheextras[h].refs)
+            self.execs = set(codeparsercache.pythoncacheextras[h].execs)
+            self.contains = {}
+            for i in codeparsercache.pythoncacheextras[h].contains:
+                self.contains[i] = set(codeparsercache.pythoncacheextras[h].contains[i])
+            return
+
+        code = compile(check_indent(str(node)), "<string>", "exec",
+                       ast.PyCF_ONLY_AST)
+
+        for n in ast.walk(code):
+            if n.__class__.__name__ == "Call":
+                self.visit_Call(n)
+
+        self.execs.update(self.var_execs)
+
+        codeparsercache.pythoncacheextras[h] = codeparsercache.newPythonCacheLine(self.references, self.execs, self.contains)
+
+class ShellParser():
+    def __init__(self, name, log):
+        self.funcdefs = set()
+        self.allexecs = set()
+        self.execs = set()
+        self.log = BufferedLogger('BitBake.Data.%s' % name, logging.DEBUG, log)
+        self.unhandled_template = "unable to handle non-literal command '%s'"
+        self.unhandled_template = "while parsing %s, %s" % (name, self.unhandled_template)
+
+    def parse_shell(self, value):
+        """Parse the supplied shell code in a string, returning the external
+        commands it executes.
+        """
+
+        h = hash(str(value))
+
+        if h in codeparsercache.shellcache:
+            self.execs = set(codeparsercache.shellcache[h].execs)
+            return self.execs
+
+        if h in codeparsercache.shellcacheextras:
+            self.execs = set(codeparsercache.shellcacheextras[h].execs)
+            return self.execs
+
+        self._parse_shell(value)
+        self.execs = set(cmd for cmd in self.allexecs if cmd not in self.funcdefs)
+
+        codeparsercache.shellcacheextras[h] = codeparsercache.newShellCacheLine(self.execs)
+
+        return self.execs
+
+    def _parse_shell(self, value):
+        try:
+            tokens, _ = pyshyacc.parse(value, eof=True, debug=False)
+        except pyshlex.NeedMore:
+            raise sherrors.ShellSyntaxError("Unexpected EOF")
+
+        for token in tokens:
+            self.process_tokens(token)
+
+    def process_tokens(self, tokens):
+        """Process a supplied portion of the syntax tree as returned by
+        pyshyacc.parse.
+        """
+
+        def function_definition(value):
+            self.funcdefs.add(value.name)
+            return [value.body], None
+
+        def case_clause(value):
+            # Element 0 of each item in the case is the list of patterns, and
+            # Element 1 of each item in the case is the list of commands to be
+            # executed when that pattern matches.
+            words = chain(*[item[0] for item in value.items])
+            cmds  = chain(*[item[1] for item in value.items])
+            return cmds, words
+
+        def if_clause(value):
+            main = chain(value.cond, value.if_cmds)
+            rest = value.else_cmds
+            if isinstance(rest, tuple) and rest[0] == "elif":
+                return chain(main, if_clause(rest[1]))
+            else:
+                return chain(main, rest)
+
+        def simple_command(value):
+            return None, chain(value.words, (assign[1] for assign in value.assigns))
+
+        token_handlers = {
+            "and_or": lambda x: ((x.left, x.right), None),
+            "async": lambda x: ([x], None),
+            "brace_group": lambda x: (x.cmds, None),
+            "for_clause": lambda x: (x.cmds, x.items),
+            "function_definition": function_definition,
+            "if_clause": lambda x: (if_clause(x), None),
+            "pipeline": lambda x: (x.commands, None),
+            "redirect_list": lambda x: ([x.cmd], None),
+            "subshell": lambda x: (x.cmds, None),
+            "while_clause": lambda x: (chain(x.condition, x.cmds), None),
+            "until_clause": lambda x: (chain(x.condition, x.cmds), None),
+            "simple_command": simple_command,
+            "case_clause": case_clause,
+        }
+
+        for token in tokens:
+            name, value = token
+            try:
+                more_tokens, words = token_handlers[name](value)
+            except KeyError:
+                raise NotImplementedError("Unsupported token type " + name)
+
+            if more_tokens:
+                self.process_tokens(more_tokens)
+
+            if words:
+                self.process_words(words)
+
+    def process_words(self, words):
+        """Process a set of 'words' in pyshyacc parlance, which includes
+        extraction of executed commands from $() blocks, as well as grabbing
+        the command name argument.
+        """
+
+        words = list(words)
+        for word in list(words):
+            wtree = pyshlex.make_wordtree(word[1])
+            for part in wtree:
+                if not isinstance(part, list):
+                    continue
+
+                if part[0] in ('`', '$('):
+                    command = pyshlex.wordtree_as_string(part[1:-1])
+                    self._parse_shell(command)
+
+                    if word[0] in ("cmd_name", "cmd_word"):
+                        if word in words:
+                            words.remove(word)
+
+        usetoken = False
+        for word in words:
+            if word[0] in ("cmd_name", "cmd_word") or \
+               (usetoken and word[0] == "TOKEN"):
+                if "=" in word[1]:
+                    usetoken = True
+                    continue
+
+                cmd = word[1]
+                if cmd.startswith("$"):
+                    self.log.debug(1, self.unhandled_template % cmd)
+                elif cmd == "eval":
+                    command = " ".join(word for _, word in words[1:])
+                    self._parse_shell(command)
+                else:
+                    self.allexecs.add(cmd)
+                break
diff --git a/bitbake/lib/bb/command.py b/bitbake/lib/bb/command.py
new file mode 100644
index 0000000..398c1d6
--- /dev/null
+++ b/bitbake/lib/bb/command.py
@@ -0,0 +1,463 @@
+"""
+BitBake 'Command' module
+
+Provide an interface to interact with the bitbake server through 'commands'
+"""
+
+# Copyright (C) 2006-2007  Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+The bitbake server takes 'commands' from its UI/commandline.
+Commands are either synchronous or asynchronous.
+Async commands return data to the client in the form of events.
+Sync commands must only return data through the function return value
+and must not trigger events, directly or indirectly.
+Commands are queued in a CommandQueue
+"""
+
+import bb.event
+import bb.cooker
+
+class CommandCompleted(bb.event.Event):
+    pass
+
+class CommandExit(bb.event.Event):
+    def  __init__(self, exitcode):
+        bb.event.Event.__init__(self)
+        self.exitcode = int(exitcode)
+
+class CommandFailed(CommandExit):
+    def __init__(self, message):
+        self.error = message
+        CommandExit.__init__(self, 1)
+
+class CommandError(Exception):
+    pass
+
+class Command:
+    """
+    A queue of asynchronous commands for bitbake
+    """
+    def __init__(self, cooker):
+        self.cooker = cooker
+        self.cmds_sync = CommandsSync()
+        self.cmds_async = CommandsAsync()
+
+        # FIXME Add lock for this
+        self.currentAsyncCommand = None
+
+    def runCommand(self, commandline, ro_only = False):
+        command = commandline.pop(0)
+        if hasattr(CommandsSync, command):
+            # Can run synchronous commands straight away
+            command_method = getattr(self.cmds_sync, command)
+            if ro_only:
+                if not hasattr(command_method, 'readonly') or False == getattr(command_method, 'readonly'):
+                    return None, "Not able to execute not readonly commands in readonly mode"
+            try:
+                if getattr(command_method, 'needconfig', False):
+                    self.cooker.updateCacheSync()
+                result = command_method(self, commandline)
+            except CommandError as exc:
+                return None, exc.args[0]
+            except (Exception, SystemExit):
+                import traceback
+                return None, traceback.format_exc()
+            else:
+                return result, None
+        if self.currentAsyncCommand is not None:
+            return None, "Busy (%s in progress)" % self.currentAsyncCommand[0]
+        if command not in CommandsAsync.__dict__:
+            return None, "No such command"
+        self.currentAsyncCommand = (command, commandline)
+        self.cooker.configuration.server_register_idlecallback(self.cooker.runCommands, self.cooker)
+        return True, None
+
+    def runAsyncCommand(self):
+        try:
+            if self.cooker.state in (bb.cooker.state.error, bb.cooker.state.shutdown, bb.cooker.state.forceshutdown):
+                # updateCache will trigger a shutdown of the parser
+                # and then raise BBHandledException triggering an exit
+                self.cooker.updateCache()
+                return False
+            if self.currentAsyncCommand is not None:
+                (command, options) = self.currentAsyncCommand
+                commandmethod = getattr(CommandsAsync, command)
+                needcache = getattr( commandmethod, "needcache" )
+                if needcache and self.cooker.state != bb.cooker.state.running:
+                    self.cooker.updateCache()
+                    return True
+                else:
+                    commandmethod(self.cmds_async, self, options)
+                    return False
+            else:
+                return False
+        except KeyboardInterrupt as exc:
+            self.finishAsyncCommand("Interrupted")
+            return False
+        except SystemExit as exc:
+            arg = exc.args[0]
+            if isinstance(arg, basestring):
+                self.finishAsyncCommand(arg)
+            else:
+                self.finishAsyncCommand("Exited with %s" % arg)
+            return False
+        except Exception as exc:
+            import traceback
+            if isinstance(exc, bb.BBHandledException):
+                self.finishAsyncCommand("")
+            else:
+                self.finishAsyncCommand(traceback.format_exc())
+            return False
+
+    def finishAsyncCommand(self, msg=None, code=None):
+        if msg or msg == "":
+            bb.event.fire(CommandFailed(msg), self.cooker.expanded_data)
+        elif code:
+            bb.event.fire(CommandExit(code), self.cooker.expanded_data)
+        else:
+            bb.event.fire(CommandCompleted(), self.cooker.expanded_data)
+        self.currentAsyncCommand = None
+        self.cooker.finishcommand()
+
+class CommandsSync:
+    """
+    A class of synchronous commands
+    These should run quickly so as not to hurt interactive performance.
+    These must not influence any running synchronous command.
+    """
+
+    def stateShutdown(self, command, params):
+        """
+        Trigger cooker 'shutdown' mode
+        """
+        command.cooker.shutdown(False)
+
+    def stateForceShutdown(self, command, params):
+        """
+        Stop the cooker
+        """
+        command.cooker.shutdown(True)
+
+    def getAllKeysWithFlags(self, command, params):
+        """
+        Returns a dump of the global state. Call with
+        variable flags to be retrieved as params.
+        """
+        flaglist = params[0]
+        return command.cooker.getAllKeysWithFlags(flaglist)
+    getAllKeysWithFlags.readonly = True
+
+    def getVariable(self, command, params):
+        """
+        Read the value of a variable from data
+        """
+        varname = params[0]
+        expand = True
+        if len(params) > 1:
+            expand = (params[1] == "True")
+
+        return command.cooker.data.getVar(varname, expand)
+    getVariable.readonly = True
+
+    def setVariable(self, command, params):
+        """
+        Set the value of variable in data
+        """
+        varname = params[0]
+        value = str(params[1])
+        command.cooker.data.setVar(varname, value)
+
+    def setConfig(self, command, params):
+        """
+        Set the value of variable in configuration
+        """
+        varname = params[0]
+        value = str(params[1])
+        setattr(command.cooker.configuration, varname, value)
+
+    def enableDataTracking(self, command, params):
+        """
+        Enable history tracking for variables
+        """
+        command.cooker.enableDataTracking()
+
+    def disableDataTracking(self, command, params):
+        """
+        Disable history tracking for variables
+        """
+        command.cooker.disableDataTracking()
+
+    def setPrePostConfFiles(self, command, params):
+        prefiles = params[0].split()
+        postfiles = params[1].split()
+        command.cooker.configuration.prefile = prefiles
+        command.cooker.configuration.postfile = postfiles
+    setPrePostConfFiles.needconfig = False
+
+    def getCpuCount(self, command, params):
+        """
+        Get the CPU count on the bitbake server
+        """
+        return bb.utils.cpu_count()
+    getCpuCount.readonly = True
+    getCpuCount.needconfig = False
+
+    def matchFile(self, command, params):
+        fMatch = params[0]
+        return command.cooker.matchFile(fMatch)
+    matchFile.needconfig = False
+
+    def generateNewImage(self, command, params):
+        image = params[0]
+        base_image = params[1]
+        package_queue = params[2]
+        timestamp = params[3]
+        description = params[4]
+        return command.cooker.generateNewImage(image, base_image,
+                        package_queue, timestamp, description)
+
+    def ensureDir(self, command, params):
+        directory = params[0]
+        bb.utils.mkdirhier(directory)
+    ensureDir.needconfig = False
+
+    def setVarFile(self, command, params):
+        """
+        Save a variable in a file; used for saving in a configuration file
+        """
+        var = params[0]
+        val = params[1]
+        default_file = params[2]
+        op = params[3]
+        command.cooker.modifyConfigurationVar(var, val, default_file, op)
+    setVarFile.needconfig = False
+
+    def removeVarFile(self, command, params):
+        """
+        Remove a variable declaration from a file
+        """
+        var = params[0]
+        command.cooker.removeConfigurationVar(var)
+    removeVarFile.needconfig = False
+
+    def createConfigFile(self, command, params):
+        """
+        Create an extra configuration file
+        """
+        name = params[0]
+        command.cooker.createConfigFile(name)
+    createConfigFile.needconfig = False
+
+    def setEventMask(self, command, params):
+        handlerNum = params[0]
+        llevel = params[1]
+        debug_domains = params[2]
+        mask = params[3]
+        return bb.event.set_UIHmask(handlerNum, llevel, debug_domains, mask)
+    setEventMask.needconfig = False
+
+    def setFeatures(self, command, params):
+        """
+        Set the cooker features to include the passed list of features
+        """
+        features = params[0]
+        command.cooker.setFeatures(features)
+    setFeatures.needconfig = False
+    # although we change the internal state of the cooker, this is transparent since
+    # we always take and leave the cooker in state.initial
+    setFeatures.readonly = True
+
+    def updateConfig(self, command, params):
+        options = params[0]
+        environment = params[1]
+        command.cooker.updateConfigOpts(options, environment)
+    updateConfig.needconfig = False
+
+class CommandsAsync:
+    """
+    A class of asynchronous commands
+    These functions communicate via generated events.
+    Any function that requires metadata parsing should be here.
+    """
+
+    def buildFile(self, command, params):
+        """
+        Build a single specified .bb file
+        """
+        bfile = params[0]
+        task = params[1]
+
+        command.cooker.buildFile(bfile, task)
+    buildFile.needcache = False
+
+    def buildTargets(self, command, params):
+        """
+        Build a set of targets
+        """
+        pkgs_to_build = params[0]
+        task = params[1]
+
+        command.cooker.buildTargets(pkgs_to_build, task)
+    buildTargets.needcache = True
+
+    def generateDepTreeEvent(self, command, params):
+        """
+        Generate an event containing the dependency information
+        """
+        pkgs_to_build = params[0]
+        task = params[1]
+
+        command.cooker.generateDepTreeEvent(pkgs_to_build, task)
+        command.finishAsyncCommand()
+    generateDepTreeEvent.needcache = True
+
+    def generateDotGraph(self, command, params):
+        """
+        Dump dependency information to disk as .dot files
+        """
+        pkgs_to_build = params[0]
+        task = params[1]
+
+        command.cooker.generateDotGraphFiles(pkgs_to_build, task)
+        command.finishAsyncCommand()
+    generateDotGraph.needcache = True
+
+    def generateTargetsTree(self, command, params):
+        """
+        Generate a tree of buildable targets.
+        If klass is provided ensure all recipes that inherit the class are
+        included in the package list.
+        If pkg_list provided use that list (plus any extras brought in by
+        klass) rather than generating a tree for all packages.
+        """
+        klass = params[0]
+        pkg_list = params[1]
+
+        command.cooker.generateTargetsTree(klass, pkg_list)
+        command.finishAsyncCommand()
+    generateTargetsTree.needcache = True
+
+    def findCoreBaseFiles(self, command, params):
+        """
+        Find certain files in COREBASE directory. i.e. Layers
+        """
+        subdir = params[0]
+        filename = params[1]
+
+        command.cooker.findCoreBaseFiles(subdir, filename)
+        command.finishAsyncCommand()
+    findCoreBaseFiles.needcache = False
+
+    def findConfigFiles(self, command, params):
+        """
+        Find config files which provide appropriate values
+        for the passed configuration variable. i.e. MACHINE
+        """
+        varname = params[0]
+
+        command.cooker.findConfigFiles(varname)
+        command.finishAsyncCommand()
+    findConfigFiles.needcache = False
+
+    def findFilesMatchingInDir(self, command, params):
+        """
+        Find implementation files matching the specified pattern
+        in the requested subdirectory of a BBPATH
+        """
+        pattern = params[0]
+        directory = params[1]
+
+        command.cooker.findFilesMatchingInDir(pattern, directory)
+        command.finishAsyncCommand()
+    findFilesMatchingInDir.needcache = False
+
+    def findConfigFilePath(self, command, params):
+        """
+        Find the path of the requested configuration file
+        """
+        configfile = params[0]
+
+        command.cooker.findConfigFilePath(configfile)
+        command.finishAsyncCommand()
+    findConfigFilePath.needcache = False
+
+    def showVersions(self, command, params):
+        """
+        Show the currently selected versions
+        """
+        command.cooker.showVersions()
+        command.finishAsyncCommand()
+    showVersions.needcache = True
+
+    def showEnvironmentTarget(self, command, params):
+        """
+        Print the environment of a target recipe
+        (needs the cache to work out which recipe to use)
+        """
+        pkg = params[0]
+
+        command.cooker.showEnvironment(None, pkg)
+        command.finishAsyncCommand()
+    showEnvironmentTarget.needcache = True
+
+    def showEnvironment(self, command, params):
+        """
+        Print the standard environment
+        or if specified the environment for a specified recipe
+        """
+        bfile = params[0]
+
+        command.cooker.showEnvironment(bfile)
+        command.finishAsyncCommand()
+    showEnvironment.needcache = False
+
+    def parseFiles(self, command, params):
+        """
+        Parse the .bb files
+        """
+        command.cooker.updateCache()
+        command.finishAsyncCommand()
+    parseFiles.needcache = True
+
+    def compareRevisions(self, command, params):
+        """
+        Parse the .bb files
+        """
+        if bb.fetch.fetcher_compare_revisions(command.cooker.data):
+            command.finishAsyncCommand(code=1)
+        else:
+            command.finishAsyncCommand()
+    compareRevisions.needcache = True
+
+    def triggerEvent(self, command, params):
+        """
+        Trigger a certain event
+        """
+        event = params[0]
+        bb.event.fire(eval(event), command.cooker.data)
+        command.currentAsyncCommand = None
+    triggerEvent.needcache = False
+
+    def resetCooker(self, command, params):
+        """
+        Reset the cooker to its initial state, thus forcing a reparse for
+        any async command that has the needcache property set to True
+        """
+        command.cooker.reset()
+        command.finishAsyncCommand()
+    resetCooker.needcache = False
+
diff --git a/bitbake/lib/bb/compat.py b/bitbake/lib/bb/compat.py
new file mode 100644
index 0000000..de1923d
--- /dev/null
+++ b/bitbake/lib/bb/compat.py
@@ -0,0 +1,6 @@
+"""Code pulled from future python versions, here for compatibility"""
+
+from collections import MutableMapping, KeysView, ValuesView, ItemsView, OrderedDict
+from functools import total_ordering
+
+
diff --git a/bitbake/lib/bb/cooker.py b/bitbake/lib/bb/cooker.py
new file mode 100644
index 0000000..a0d7d59
--- /dev/null
+++ b/bitbake/lib/bb/cooker.py
@@ -0,0 +1,2139 @@
+#!/usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Copyright (C) 2003, 2004  Chris Larson
+# Copyright (C) 2003, 2004  Phil Blundell
+# Copyright (C) 2003 - 2005 Michael 'Mickey' Lauer
+# Copyright (C) 2005        Holger Hans Peter Freyther
+# Copyright (C) 2005        ROAD GmbH
+# Copyright (C) 2006 - 2007 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from __future__ import print_function
+import sys, os, glob, os.path, re, time
+import atexit
+import itertools
+import logging
+import multiprocessing
+import sre_constants
+import threading
+from cStringIO import StringIO
+from contextlib import closing
+from functools import wraps
+from collections import defaultdict
+import bb, bb.exceptions, bb.command
+from bb import utils, data, parse, event, cache, providers, taskdata, runqueue, build
+import Queue
+import signal
+import subprocess
+import errno
+import prserv.serv
+import pyinotify
+
+logger      = logging.getLogger("BitBake")
+collectlog  = logging.getLogger("BitBake.Collection")
+buildlog    = logging.getLogger("BitBake.Build")
+parselog    = logging.getLogger("BitBake.Parsing")
+providerlog = logging.getLogger("BitBake.Provider")
+
+class NoSpecificMatch(bb.BBHandledException):
+    """
+    Exception raised when no or multiple file matches are found
+    """
+
+class NothingToBuild(Exception):
+    """
+    Exception raised when there is nothing to build
+    """
+
+class CollectionError(bb.BBHandledException):
+    """
+    Exception raised when layer configuration is incorrect
+    """
+
+class state:
+    initial, parsing, running, shutdown, forceshutdown, stopped, error = range(7)
+
+
+class SkippedPackage:
+    def __init__(self, info = None, reason = None):
+        self.pn = None
+        self.skipreason = None
+        self.provides = None
+        self.rprovides = None
+
+        if info:
+            self.pn = info.pn
+            self.skipreason = info.skipreason
+            self.provides = info.provides
+            self.rprovides = info.rprovides
+        elif reason:
+            self.skipreason = reason
+
+
+class CookerFeatures(object):
+    _feature_list = [HOB_EXTRA_CACHES, SEND_DEPENDS_TREE, BASEDATASTORE_TRACKING, SEND_SANITYEVENTS] = range(4)
+
+    def __init__(self):
+        self._features=set()
+
+    def setFeature(self, f):
+        # validate we got a request for a feature we support
+        if f not in CookerFeatures._feature_list:
+            return
+        self._features.add(f)
+
+    def __contains__(self, f):
+        return f in self._features
+
+    def __iter__(self):
+        return self._features.__iter__()
+
+    def next(self):
+        return self._features.next()
+
+
+#============================================================================#
+# BBCooker
+#============================================================================#
+class BBCooker:
+    """
+    Manages one bitbake build run
+    """
+
+    def __init__(self, configuration, featureSet=None):
+        self.recipecache = None
+        self.skiplist = {}
+        self.featureset = CookerFeatures()
+        if featureSet:
+            for f in featureSet:
+                self.featureset.setFeature(f)
+
+        self.configuration = configuration
+
+        self.configwatcher = pyinotify.WatchManager()
+        self.configwatcher.bbseen = []
+        self.configwatcher.bbwatchedfiles = []
+        self.confignotifier = pyinotify.Notifier(self.configwatcher, self.config_notifications)
+        self.watchmask = pyinotify.IN_CLOSE_WRITE | pyinotify.IN_CREATE | pyinotify.IN_DELETE | \
+                         pyinotify.IN_DELETE_SELF | pyinotify.IN_MODIFY | pyinotify.IN_MOVE_SELF | \
+                         pyinotify.IN_MOVED_FROM | pyinotify.IN_MOVED_TO 
+        self.watcher = pyinotify.WatchManager()
+        self.watcher.bbseen = []
+        self.watcher.bbwatchedfiles = []
+        self.notifier = pyinotify.Notifier(self.watcher, self.notifications)
+
+
+        self.initConfigurationData()
+
+        self.inotify_modified_files = []
+
+        def _process_inotify_updates(server, notifier_list, abort):
+            for n in notifier_list:
+                if n.check_events(timeout=0):
+                    # read notified events and enqeue them
+                    n.read_events()
+                    n.process_events()
+            return 1.0
+
+        self.configuration.server_register_idlecallback(_process_inotify_updates, [self.confignotifier, self.notifier])
+
+        self.baseconfig_valid = True
+        self.parsecache_valid = False
+
+        # Take a lock so only one copy of bitbake can run against a given build
+        # directory at a time
+        if not self.lockBitbake():
+            bb.fatal("Only one copy of bitbake should be run against a build directory")
+        try:
+            self.lock.seek(0)
+            self.lock.truncate()
+            if len(configuration.interface) >= 2:
+                self.lock.write("%s:%s\n" % (configuration.interface[0], configuration.interface[1]));
+            self.lock.flush()
+        except:
+            pass
+
+        # TOSTOP must not be set or our children will hang when they output
+        fd = sys.stdout.fileno()
+        if os.isatty(fd):
+            import termios
+            tcattr = termios.tcgetattr(fd)
+            if tcattr[3] & termios.TOSTOP:
+                buildlog.info("The terminal had the TOSTOP bit set, clearing...")
+                tcattr[3] = tcattr[3] & ~termios.TOSTOP
+                termios.tcsetattr(fd, termios.TCSANOW, tcattr)
+
+        self.command = bb.command.Command(self)
+        self.state = state.initial
+
+        self.parser = None
+
+        signal.signal(signal.SIGTERM, self.sigterm_exception)
+        # Let SIGHUP exit as SIGTERM
+        signal.signal(signal.SIGHUP, self.sigterm_exception)
+
+    def config_notifications(self, event):
+        if not event.pathname in self.configwatcher.bbwatchedfiles:
+            return
+        if not event.path in self.inotify_modified_files:
+            self.inotify_modified_files.append(event.path)
+        self.baseconfig_valid = False
+
+    def notifications(self, event):
+        if not event.path in self.inotify_modified_files:
+            self.inotify_modified_files.append(event.path)
+        self.parsecache_valid = False
+
+    def add_filewatch(self, deps, watcher=None):
+        if not watcher:
+            watcher = self.watcher
+        for i in deps:
+            watcher.bbwatchedfiles.append(i[0])
+            f = os.path.dirname(i[0])
+            if f in watcher.bbseen:
+                continue
+            watcher.bbseen.append(f)
+            watchtarget = None
+            while True:
+                # We try and add watches for files that don't exist but if they did, would influence
+                # the parser. The parent directory of these files may not exist, in which case we need 
+                # to watch any parent that does exist for changes.
+                try:
+                    watcher.add_watch(f, self.watchmask, quiet=False)
+                    if watchtarget:
+                        watcher.bbwatchedfiles.append(watchtarget)
+                    break
+                except pyinotify.WatchManagerError as e:
+                    if 'ENOENT' in str(e):
+                        watchtarget = f
+                        f = os.path.dirname(f)
+                        if f in watcher.bbseen:
+                            break
+                        watcher.bbseen.append(f)
+                        continue
+                    if 'ENOSPC' in str(e):
+                        providerlog.error("No space left on device or exceeds fs.inotify.max_user_watches?")
+                        providerlog.error("To check max_user_watches: sysctl -n fs.inotify.max_user_watches.")
+                        providerlog.error("To modify max_user_watches: sysctl -n -w fs.inotify.max_user_watches=<value>.")
+                        providerlog.error("Root privilege is required to modify max_user_watches.")
+                    raise
+
+    def sigterm_exception(self, signum, stackframe):
+        if signum == signal.SIGTERM:
+            bb.warn("Cooker recieved SIGTERM, shutting down...")
+        elif signum == signal.SIGHUP:
+            bb.warn("Cooker recieved SIGHUP, shutting down...")
+        self.state = state.forceshutdown
+
+    def setFeatures(self, features):
+        # we only accept a new feature set if we're in state initial, so we can reset without problems
+        if not self.state in [state.initial, state.shutdown, state.forceshutdown, state.stopped, state.error]:
+            raise Exception("Illegal state for feature set change")
+        original_featureset = list(self.featureset)
+        for feature in features:
+            self.featureset.setFeature(feature)
+        bb.debug(1, "Features set %s (was %s)" % (original_featureset, list(self.featureset)))
+        if (original_featureset != list(self.featureset)) and self.state != state.error:
+            self.reset()
+
+    def initConfigurationData(self):
+
+        self.state = state.initial
+        self.caches_array = []
+
+        if CookerFeatures.BASEDATASTORE_TRACKING in self.featureset:
+            self.enableDataTracking()
+
+        all_extra_cache_names = []
+        # We hardcode all known cache types in a single place, here.
+        if CookerFeatures.HOB_EXTRA_CACHES in self.featureset:
+            all_extra_cache_names.append("bb.cache_extra:HobRecipeInfo")
+
+        caches_name_array = ['bb.cache:CoreRecipeInfo'] + all_extra_cache_names
+
+        # At least CoreRecipeInfo will be loaded, so caches_array will never be empty!
+        # This is the entry point, no further check needed!
+        for var in caches_name_array:
+            try:
+                module_name, cache_name = var.split(':')
+                module = __import__(module_name, fromlist=(cache_name,))
+                self.caches_array.append(getattr(module, cache_name))
+            except ImportError as exc:
+                logger.critical("Unable to import extra RecipeInfo '%s' from '%s': %s" % (cache_name, module_name, exc))
+                sys.exit("FATAL: Failed to import extra cache class '%s'." % cache_name)
+
+        self.databuilder = bb.cookerdata.CookerDataBuilder(self.configuration, False)
+        self.databuilder.parseBaseConfiguration()
+        self.data = self.databuilder.data
+        self.data_hash = self.databuilder.data_hash
+
+
+        # we log all events to a file if so directed
+        if self.configuration.writeeventlog:
+            import json, pickle
+            DEFAULT_EVENTFILE = self.configuration.writeeventlog
+            class EventLogWriteHandler():
+
+                class EventWriter():
+                    def __init__(self, cooker):
+                        self.file_inited = None
+                        self.cooker = cooker
+                        self.event_queue = []
+
+                    def init_file(self):
+                        try:
+                            # delete the old log
+                            os.remove(DEFAULT_EVENTFILE)
+                        except:
+                            pass
+
+                        # write current configuration data
+                        with open(DEFAULT_EVENTFILE, "w") as f:
+                            f.write("%s\n" % json.dumps({ "allvariables" : self.cooker.getAllKeysWithFlags(["doc", "func"])}))
+
+                    def write_event(self, event):
+                        with open(DEFAULT_EVENTFILE, "a") as f:
+                            try:
+                                f.write("%s\n" % json.dumps({"class":event.__module__ + "." + event.__class__.__name__, "vars":json.dumps(pickle.dumps(event)) }))
+                            except Exception as e:
+                                import traceback
+                                print(e, traceback.format_exc(e))
+
+
+                    def send(self, event):
+                        event_class = event.__module__ + "." + event.__class__.__name__
+
+                        # init on bb.event.BuildStarted
+                        if self.file_inited is None:
+                            if  event_class == "bb.event.BuildStarted":
+                                self.init_file()
+                                self.file_inited = True
+
+                                # write pending events
+                                for e in self.event_queue:
+                                    self.write_event(e)
+
+                                # also write the current event
+                                self.write_event(event)
+
+                            else:
+                                # queue all events until the file is inited
+                                self.event_queue.append(event)
+
+                        else:
+                            # we have the file, just write the event
+                            self.write_event(event)
+
+                # set our handler's event processor
+                event = EventWriter(self)       # self is the cooker here
+
+
+            # set up cooker features for this mock UI handler
+
+            # we need to write the dependency tree in the log
+            self.featureset.setFeature(CookerFeatures.SEND_DEPENDS_TREE)
+            # register the log file writer as UI Handler
+            bb.event.register_UIHhandler(EventLogWriteHandler())
+
+
+        #
+        # Copy of the data store which has been expanded.
+        # Used for firing events and accessing variables where expansion needs to be accounted for
+        #
+        self.expanded_data = bb.data.createCopy(self.data)
+        bb.data.update_data(self.expanded_data)
+        bb.parse.init_parser(self.expanded_data)
+
+        if CookerFeatures.BASEDATASTORE_TRACKING in self.featureset:
+            self.disableDataTracking()
+
+        self.data.renameVar("__depends", "__base_depends")
+        self.add_filewatch(self.data.getVar("__base_depends", False), self.configwatcher)
+
+
+    def enableDataTracking(self):
+        self.configuration.tracking = True
+        if hasattr(self, "data"):
+            self.data.enableTracking()
+
+    def disableDataTracking(self):
+        self.configuration.tracking = False
+        if hasattr(self, "data"):
+            self.data.disableTracking()
+
+    def modifyConfigurationVar(self, var, val, default_file, op):
+        if op == "append":
+            self.appendConfigurationVar(var, val, default_file)
+        elif op == "set":
+            self.saveConfigurationVar(var, val, default_file, "=")
+        elif op == "earlyAssign":
+            self.saveConfigurationVar(var, val, default_file, "?=")
+
+
+    def appendConfigurationVar(self, var, val, default_file):
+        #add append var operation to the end of default_file
+        default_file = bb.cookerdata.findConfigFile(default_file, self.data)
+
+        total = "#added by hob"
+        total += "\n%s += \"%s\"\n" % (var, val)
+
+        with open(default_file, 'a') as f:
+            f.write(total)
+
+        #add to history
+        loginfo = {"op":"append", "file":default_file, "line":total.count("\n")}
+        self.data.appendVar(var, val, **loginfo)
+
+    def saveConfigurationVar(self, var, val, default_file, op):
+
+        replaced = False
+        #do not save if nothing changed
+        if str(val) == self.data.getVar(var, False):
+            return
+
+        conf_files = self.data.varhistory.get_variable_files(var)
+
+        #format the value when it is a list
+        if isinstance(val, list):
+            listval = ""
+            for value in val:
+                listval += "%s   " % value
+            val = listval
+
+        topdir = self.data.getVar("TOPDIR", False)
+
+        #comment or replace operations made on var
+        for conf_file in conf_files:
+            if topdir in conf_file:
+                with open(conf_file, 'r') as f:
+                    contents = f.readlines()
+
+                lines = self.data.varhistory.get_variable_lines(var, conf_file)
+                for line in lines:
+                    total = ""
+                    i = 0
+                    for c in contents:
+                        total += c
+                        i = i + 1
+                        if i==int(line):
+                            end_index = len(total)
+                    index = total.rfind(var, 0, end_index)
+
+                    begin_line = total.count("\n",0,index)
+                    end_line = int(line)
+
+                    #check if the variable was saved before in the same way
+                    #if true it replace the place where the variable was declared
+                    #else it comments it
+                    if contents[begin_line-1]== "#added by hob\n":
+                        contents[begin_line] = "%s %s \"%s\"\n" % (var, op, val)
+                        replaced = True
+                    else:
+                        for ii in range(begin_line, end_line):
+                            contents[ii] = "#" + contents[ii]
+
+                with open(conf_file, 'w') as f:
+                    f.writelines(contents)
+
+        if replaced == False:
+            #remove var from history
+            self.data.varhistory.del_var_history(var)
+
+            #add var to the end of default_file
+            default_file = bb.cookerdata.findConfigFile(default_file, self.data)
+
+            #add the variable on a single line, to be easy to replace the second time
+            total = "\n#added by hob"
+            total += "\n%s %s \"%s\"\n" % (var, op, val)
+
+            with open(default_file, 'a') as f:
+                f.write(total)
+
+            #add to history
+            loginfo = {"op":"set", "file":default_file, "line":total.count("\n")}
+            self.data.setVar(var, val, **loginfo)
+
+    def removeConfigurationVar(self, var):
+        conf_files = self.data.varhistory.get_variable_files(var)
+        topdir = self.data.getVar("TOPDIR", False)
+
+        for conf_file in conf_files:
+            if topdir in conf_file:
+                with open(conf_file, 'r') as f:
+                    contents = f.readlines()
+
+                lines = self.data.varhistory.get_variable_lines(var, conf_file)
+                for line in lines:
+                    total = ""
+                    i = 0
+                    for c in contents:
+                        total += c
+                        i = i + 1
+                        if i==int(line):
+                            end_index = len(total)
+                    index = total.rfind(var, 0, end_index)
+
+                    begin_line = total.count("\n",0,index)
+
+                    #check if the variable was saved before in the same way
+                    if contents[begin_line-1]== "#added by hob\n":
+                        contents[begin_line-1] = contents[begin_line] = "\n"
+                    else:
+                        contents[begin_line] = "\n"
+                    #remove var from history
+                    self.data.varhistory.del_var_history(var, conf_file, line)
+                    #remove variable
+                    self.data.delVar(var)
+
+                with open(conf_file, 'w') as f:
+                    f.writelines(contents)
+
+    def createConfigFile(self, name):
+        path = os.getcwd()
+        confpath = os.path.join(path, "conf", name)
+        open(confpath, 'w').close()
+
+    def parseConfiguration(self):
+        # Set log file verbosity
+        verboselogs = bb.utils.to_boolean(self.data.getVar("BB_VERBOSE_LOGS", False))
+        if verboselogs:
+            bb.msg.loggerVerboseLogs = True
+
+        # Change nice level if we're asked to
+        nice = self.data.getVar("BB_NICE_LEVEL", True)
+        if nice:
+            curnice = os.nice(0)
+            nice = int(nice) - curnice
+            buildlog.verbose("Renice to %s " % os.nice(nice))
+
+        if self.recipecache:
+            del self.recipecache
+        self.recipecache = bb.cache.CacheData(self.caches_array)
+
+        self.handleCollections( self.data.getVar("BBFILE_COLLECTIONS", True) )
+
+    def updateConfigOpts(self, options, environment):
+        clean = True
+        for o in options:
+            if o in ['prefile', 'postfile']:
+                clean = False
+            setattr(self.configuration, o, options[o])
+        for k in bb.utils.approved_variables():
+            if k in environment and k not in self.configuration.env:
+                logger.debug(1, "Updating environment variable %s to %s" % (k, environment[k]))
+                self.configuration.env[k] = environment[k]
+                clean = False
+            if k in self.configuration.env and k not in environment:
+                logger.debug(1, "Updating environment variable %s (deleted)" % (k))
+                del self.configuration.env[k]
+                clean = False
+            if k not in self.configuration.env and k not in environment:
+                 continue
+            if environment[k] != self.configuration.env[k]:
+                logger.debug(1, "Updating environment variable %s to %s" % (k, environment[k]))
+                self.configuration.env[k] = environment[k]
+                clean = False
+        if not clean:
+            logger.debug(1, "Base environment change, triggering reparse")
+            self.baseconfig_valid = False        
+            self.reset()
+
+    def runCommands(self, server, data, abort):
+        """
+        Run any queued asynchronous command
+        This is done by the idle handler so it runs in true context rather than
+        tied to any UI.
+        """
+
+        return self.command.runAsyncCommand()
+
+    def showVersions(self):
+
+        pkg_pn = self.recipecache.pkg_pn
+        (latest_versions, preferred_versions) = bb.providers.findProviders(self.data, self.recipecache, pkg_pn)
+
+        logger.plain("%-35s %25s %25s", "Recipe Name", "Latest Version", "Preferred Version")
+        logger.plain("%-35s %25s %25s\n", "===========", "==============", "=================")
+
+        for p in sorted(pkg_pn):
+            pref = preferred_versions[p]
+            latest = latest_versions[p]
+
+            prefstr = pref[0][0] + ":" + pref[0][1] + '-' + pref[0][2]
+            lateststr = latest[0][0] + ":" + latest[0][1] + "-" + latest[0][2]
+
+            if pref == latest:
+                prefstr = ""
+
+            logger.plain("%-35s %25s %25s", p, lateststr, prefstr)
+
+    def showEnvironment(self, buildfile=None, pkgs_to_build=None):
+        """
+        Show the outer or per-recipe environment
+        """
+        fn = None
+        envdata = None
+        if not pkgs_to_build:
+            pkgs_to_build = []
+
+        if buildfile:
+            # Parse the configuration here. We need to do it explicitly here since
+            # this showEnvironment() code path doesn't use the cache
+            self.parseConfiguration()
+
+            fn, cls = bb.cache.Cache.virtualfn2realfn(buildfile)
+            fn = self.matchFile(fn)
+            fn = bb.cache.Cache.realfn2virtual(fn, cls)
+        elif len(pkgs_to_build) == 1:
+            ignore = self.expanded_data.getVar("ASSUME_PROVIDED", True) or ""
+            if pkgs_to_build[0] in set(ignore.split()):
+                bb.fatal("%s is in ASSUME_PROVIDED" % pkgs_to_build[0])
+
+            taskdata, runlist, pkgs_to_build = self.buildTaskData(pkgs_to_build, None, self.configuration.abort, allowincomplete=True)
+
+            targetid = taskdata.getbuild_id(pkgs_to_build[0])
+            fnid = taskdata.build_targets[targetid][0]
+            fn = taskdata.fn_index[fnid]
+        else:
+            envdata = self.data
+
+        if fn:
+            try:
+                envdata = bb.cache.Cache.loadDataFull(fn, self.collection.get_file_appends(fn), self.data)
+            except Exception as e:
+                parselog.exception("Unable to read %s", fn)
+                raise
+
+        # Display history
+        with closing(StringIO()) as env:
+            self.data.inchistory.emit(env)
+            logger.plain(env.getvalue())
+
+        # emit variables and shell functions
+        data.update_data(envdata)
+        with closing(StringIO()) as env:
+            data.emit_env(env, envdata, True)
+            logger.plain(env.getvalue())
+
+        # emit the metadata which isnt valid shell
+        data.expandKeys(envdata)
+        for e in envdata.keys():
+            if data.getVarFlag( e, 'python', envdata ):
+                logger.plain("\npython %s () {\n%s}\n", e, envdata.getVar(e, True))
+
+
+    def buildTaskData(self, pkgs_to_build, task, abort, allowincomplete=False):
+        """
+        Prepare a runqueue and taskdata object for iteration over pkgs_to_build
+        """
+        bb.event.fire(bb.event.TreeDataPreparationStarted(), self.data)
+
+        # A task of None means use the default task
+        if task is None:
+            task = self.configuration.cmd
+
+        fulltargetlist = self.checkPackages(pkgs_to_build)
+
+        localdata = data.createCopy(self.data)
+        bb.data.update_data(localdata)
+        bb.data.expandKeys(localdata)
+        taskdata = bb.taskdata.TaskData(abort, skiplist=self.skiplist, allowincomplete=allowincomplete)
+
+        current = 0
+        runlist = []
+        for k in fulltargetlist:
+            ktask = task
+            if ":do_" in k:
+                k2 = k.split(":do_")
+                k = k2[0]
+                ktask = k2[1]
+            taskdata.add_provider(localdata, self.recipecache, k)
+            current += 1
+            if not ktask.startswith("do_"):
+                ktask = "do_%s" % ktask
+            runlist.append([k, ktask])
+            bb.event.fire(bb.event.TreeDataPreparationProgress(current, len(fulltargetlist)), self.data)
+        taskdata.add_unresolved(localdata, self.recipecache)
+        bb.event.fire(bb.event.TreeDataPreparationCompleted(len(fulltargetlist)), self.data)
+        return taskdata, runlist, fulltargetlist
+
+    def prepareTreeData(self, pkgs_to_build, task):
+        """
+        Prepare a runqueue and taskdata object for iteration over pkgs_to_build
+        """
+
+        # We set abort to False here to prevent unbuildable targets raising
+        # an exception when we're just generating data
+        taskdata, runlist, pkgs_to_build = self.buildTaskData(pkgs_to_build, task, False, allowincomplete=True)
+
+        return runlist, taskdata
+
+    ######## WARNING : this function requires cache_extra to be enabled ########
+
+    def generateTaskDepTreeData(self, pkgs_to_build, task):
+        """
+        Create a dependency graph of pkgs_to_build including reverse dependency
+        information.
+        """
+        runlist, taskdata = self.prepareTreeData(pkgs_to_build, task)
+        rq = bb.runqueue.RunQueue(self, self.data, self.recipecache, taskdata, runlist)
+        rq.rqdata.prepare()
+        return self.buildDependTree(rq, taskdata)
+
+
+    def buildDependTree(self, rq, taskdata):
+        seen_fnids = []
+        depend_tree = {}
+        depend_tree["depends"] = {}
+        depend_tree["tdepends"] = {}
+        depend_tree["pn"] = {}
+        depend_tree["rdepends-pn"] = {}
+        depend_tree["packages"] = {}
+        depend_tree["rdepends-pkg"] = {}
+        depend_tree["rrecs-pkg"] = {}
+        depend_tree["layer-priorities"] = self.recipecache.bbfile_config_priorities
+
+        for task in xrange(len(rq.rqdata.runq_fnid)):
+            taskname = rq.rqdata.runq_task[task]
+            fnid = rq.rqdata.runq_fnid[task]
+            fn = taskdata.fn_index[fnid]
+            pn = self.recipecache.pkg_fn[fn]
+            version  = "%s:%s-%s" % self.recipecache.pkg_pepvpr[fn]
+            if pn not in depend_tree["pn"]:
+                depend_tree["pn"][pn] = {}
+                depend_tree["pn"][pn]["filename"] = fn
+                depend_tree["pn"][pn]["version"] = version
+                depend_tree["pn"][pn]["inherits"] = self.recipecache.inherits.get(fn, None)
+
+                # if we have extra caches, list all attributes they bring in
+                extra_info = []
+                for cache_class in self.caches_array:
+                    if type(cache_class) is type and issubclass(cache_class, bb.cache.RecipeInfoCommon) and hasattr(cache_class, 'cachefields'):
+                        cachefields = getattr(cache_class, 'cachefields', [])
+                        extra_info = extra_info + cachefields
+
+                # for all attributes stored, add them to the dependency tree
+                for ei in extra_info:
+                    depend_tree["pn"][pn][ei] = vars(self.recipecache)[ei][fn]
+
+
+            for dep in rq.rqdata.runq_depends[task]:
+                depfn = taskdata.fn_index[rq.rqdata.runq_fnid[dep]]
+                deppn = self.recipecache.pkg_fn[depfn]
+                dotname = "%s.%s" % (pn, rq.rqdata.runq_task[task])
+                if not dotname in depend_tree["tdepends"]:
+                    depend_tree["tdepends"][dotname] = []
+                depend_tree["tdepends"][dotname].append("%s.%s" % (deppn, rq.rqdata.runq_task[dep]))
+            if fnid not in seen_fnids:
+                seen_fnids.append(fnid)
+                packages = []
+
+                depend_tree["depends"][pn] = []
+                for dep in taskdata.depids[fnid]:
+                    depend_tree["depends"][pn].append(taskdata.build_names_index[dep])
+
+                depend_tree["rdepends-pn"][pn] = []
+                for rdep in taskdata.rdepids[fnid]:
+                    depend_tree["rdepends-pn"][pn].append(taskdata.run_names_index[rdep])
+
+                rdepends = self.recipecache.rundeps[fn]
+                for package in rdepends:
+                    depend_tree["rdepends-pkg"][package] = []
+                    for rdepend in rdepends[package]:
+                        depend_tree["rdepends-pkg"][package].append(rdepend)
+                    packages.append(package)
+
+                rrecs = self.recipecache.runrecs[fn]
+                for package in rrecs:
+                    depend_tree["rrecs-pkg"][package] = []
+                    for rdepend in rrecs[package]:
+                        depend_tree["rrecs-pkg"][package].append(rdepend)
+                    if not package in packages:
+                        packages.append(package)
+
+                for package in packages:
+                    if package not in depend_tree["packages"]:
+                        depend_tree["packages"][package] = {}
+                        depend_tree["packages"][package]["pn"] = pn
+                        depend_tree["packages"][package]["filename"] = fn
+                        depend_tree["packages"][package]["version"] = version
+
+        return depend_tree
+
+    ######## WARNING : this function requires cache_extra to be enabled ########
+    def generatePkgDepTreeData(self, pkgs_to_build, task):
+        """
+        Create a dependency tree of pkgs_to_build, returning the data.
+        """
+        _, taskdata = self.prepareTreeData(pkgs_to_build, task)
+        tasks_fnid = []
+        if len(taskdata.tasks_name) != 0:
+            for task in xrange(len(taskdata.tasks_name)):
+                tasks_fnid.append(taskdata.tasks_fnid[task])
+
+        seen_fnids = []
+        depend_tree = {}
+        depend_tree["depends"] = {}
+        depend_tree["pn"] = {}
+        depend_tree["rdepends-pn"] = {}
+        depend_tree["rdepends-pkg"] = {}
+        depend_tree["rrecs-pkg"] = {}
+
+        # if we have extra caches, list all attributes they bring in
+        extra_info = []
+        for cache_class in self.caches_array:
+            if type(cache_class) is type and issubclass(cache_class, bb.cache.RecipeInfoCommon) and hasattr(cache_class, 'cachefields'):
+                cachefields = getattr(cache_class, 'cachefields', [])
+                extra_info = extra_info + cachefields
+
+        for task in xrange(len(tasks_fnid)):
+            fnid = tasks_fnid[task]
+            fn = taskdata.fn_index[fnid]
+            pn = self.recipecache.pkg_fn[fn]
+
+            if pn not in depend_tree["pn"]:
+                depend_tree["pn"][pn] = {}
+                depend_tree["pn"][pn]["filename"] = fn
+                version  = "%s:%s-%s" % self.recipecache.pkg_pepvpr[fn]
+                depend_tree["pn"][pn]["version"] = version
+                rdepends = self.recipecache.rundeps[fn]
+                rrecs = self.recipecache.runrecs[fn]
+                depend_tree["pn"][pn]["inherits"] = self.recipecache.inherits.get(fn, None)
+
+                # for all extra attributes stored, add them to the dependency tree
+                for ei in extra_info:
+                    depend_tree["pn"][pn][ei] = vars(self.recipecache)[ei][fn]
+
+            if fnid not in seen_fnids:
+                seen_fnids.append(fnid)
+
+                depend_tree["depends"][pn] = []
+                for dep in taskdata.depids[fnid]:
+                    item = taskdata.build_names_index[dep]
+                    pn_provider = ""
+                    targetid = taskdata.getbuild_id(item)
+                    if targetid in taskdata.build_targets and taskdata.build_targets[targetid]:
+                        id = taskdata.build_targets[targetid][0]
+                        fn_provider = taskdata.fn_index[id]
+                        pn_provider = self.recipecache.pkg_fn[fn_provider]
+                    else:
+                        pn_provider = item
+                    depend_tree["depends"][pn].append(pn_provider)
+
+                depend_tree["rdepends-pn"][pn] = []
+                for rdep in taskdata.rdepids[fnid]:
+                    item = taskdata.run_names_index[rdep]
+                    pn_rprovider = ""
+                    targetid = taskdata.getrun_id(item)
+                    if targetid in taskdata.run_targets and taskdata.run_targets[targetid]:
+                        id = taskdata.run_targets[targetid][0]
+                        fn_rprovider = taskdata.fn_index[id]
+                        pn_rprovider = self.recipecache.pkg_fn[fn_rprovider]
+                    else:
+                        pn_rprovider = item
+                    depend_tree["rdepends-pn"][pn].append(pn_rprovider)
+
+                depend_tree["rdepends-pkg"].update(rdepends)
+                depend_tree["rrecs-pkg"].update(rrecs)
+
+        return depend_tree
+
+    def generateDepTreeEvent(self, pkgs_to_build, task):
+        """
+        Create a task dependency graph of pkgs_to_build.
+        Generate an event with the result
+        """
+        depgraph = self.generateTaskDepTreeData(pkgs_to_build, task)
+        bb.event.fire(bb.event.DepTreeGenerated(depgraph), self.data)
+
+    def generateDotGraphFiles(self, pkgs_to_build, task):
+        """
+        Create a task dependency graph of pkgs_to_build.
+        Save the result to a set of .dot files.
+        """
+
+        depgraph = self.generateTaskDepTreeData(pkgs_to_build, task)
+
+        # Prints a flattened form of package-depends below where subpackages of a package are merged into the main pn
+        depends_file = file('pn-depends.dot', 'w' )
+        buildlist_file = file('pn-buildlist', 'w' )
+        print("digraph depends {", file=depends_file)
+        for pn in depgraph["pn"]:
+            fn = depgraph["pn"][pn]["filename"]
+            version = depgraph["pn"][pn]["version"]
+            print('"%s" [label="%s %s\\n%s"]' % (pn, pn, version, fn), file=depends_file)
+            print("%s" % pn, file=buildlist_file)
+        buildlist_file.close()
+        logger.info("PN build list saved to 'pn-buildlist'")
+        for pn in depgraph["depends"]:
+            for depend in depgraph["depends"][pn]:
+                print('"%s" -> "%s"' % (pn, depend), file=depends_file)
+        for pn in depgraph["rdepends-pn"]:
+            for rdepend in depgraph["rdepends-pn"][pn]:
+                print('"%s" -> "%s" [style=dashed]' % (pn, rdepend), file=depends_file)
+        print("}", file=depends_file)
+        logger.info("PN dependencies saved to 'pn-depends.dot'")
+
+        depends_file = file('package-depends.dot', 'w' )
+        print("digraph depends {", file=depends_file)
+        for package in depgraph["packages"]:
+            pn = depgraph["packages"][package]["pn"]
+            fn = depgraph["packages"][package]["filename"]
+            version = depgraph["packages"][package]["version"]
+            if package == pn:
+                print('"%s" [label="%s %s\\n%s"]' % (pn, pn, version, fn), file=depends_file)
+            else:
+                print('"%s" [label="%s(%s) %s\\n%s"]' % (package, package, pn, version, fn), file=depends_file)
+            for depend in depgraph["depends"][pn]:
+                print('"%s" -> "%s"' % (package, depend), file=depends_file)
+        for package in depgraph["rdepends-pkg"]:
+            for rdepend in depgraph["rdepends-pkg"][package]:
+                print('"%s" -> "%s" [style=dashed]' % (package, rdepend), file=depends_file)
+        for package in depgraph["rrecs-pkg"]:
+            for rdepend in depgraph["rrecs-pkg"][package]:
+                print('"%s" -> "%s" [style=dashed]' % (package, rdepend), file=depends_file)
+        print("}", file=depends_file)
+        logger.info("Package dependencies saved to 'package-depends.dot'")
+
+        tdepends_file = file('task-depends.dot', 'w' )
+        print("digraph depends {", file=tdepends_file)
+        for task in depgraph["tdepends"]:
+            (pn, taskname) = task.rsplit(".", 1)
+            fn = depgraph["pn"][pn]["filename"]
+            version = depgraph["pn"][pn]["version"]
+            print('"%s.%s" [label="%s %s\\n%s\\n%s"]' % (pn, taskname, pn, taskname, version, fn), file=tdepends_file)
+            for dep in depgraph["tdepends"][task]:
+                print('"%s" -> "%s"' % (task, dep), file=tdepends_file)
+        print("}", file=tdepends_file)
+        logger.info("Task dependencies saved to 'task-depends.dot'")
+
+    def show_appends_with_no_recipes(self):
+        # Determine which bbappends haven't been applied
+
+        # First get list of recipes, including skipped
+        recipefns = self.recipecache.pkg_fn.keys()
+        recipefns.extend(self.skiplist.keys())
+
+        # Work out list of bbappends that have been applied
+        applied_appends = []
+        for fn in recipefns:
+            applied_appends.extend(self.collection.get_file_appends(fn))
+
+        appends_without_recipes = []
+        for _, appendfn in self.collection.bbappends:
+            if not appendfn in applied_appends:
+                appends_without_recipes.append(appendfn)
+
+        if appends_without_recipes:
+            msg = 'No recipes available for:\n  %s' % '\n  '.join(appends_without_recipes)
+            warn_only = self.data.getVar("BB_DANGLINGAPPENDS_WARNONLY", \
+                 False) or "no"
+            if warn_only.lower() in ("1", "yes", "true"):
+                bb.warn(msg)
+            else:
+                bb.fatal(msg)
+
+    def handlePrefProviders(self):
+
+        localdata = data.createCopy(self.data)
+        bb.data.update_data(localdata)
+        bb.data.expandKeys(localdata)
+
+        # Handle PREFERRED_PROVIDERS
+        for p in (localdata.getVar('PREFERRED_PROVIDERS', True) or "").split():
+            try:
+                (providee, provider) = p.split(':')
+            except:
+                providerlog.critical("Malformed option in PREFERRED_PROVIDERS variable: %s" % p)
+                continue
+            if providee in self.recipecache.preferred and self.recipecache.preferred[providee] != provider:
+                providerlog.error("conflicting preferences for %s: both %s and %s specified", providee, provider, self.recipecache.preferred[providee])
+            self.recipecache.preferred[providee] = provider
+
+    def findCoreBaseFiles(self, subdir, configfile):
+        corebase = self.data.getVar('COREBASE', True) or ""
+        paths = []
+        for root, dirs, files in os.walk(corebase + '/' + subdir):
+            for d in dirs:
+                configfilepath = os.path.join(root, d, configfile)
+                if os.path.exists(configfilepath):
+                    paths.append(os.path.join(root, d))
+
+        if paths:
+            bb.event.fire(bb.event.CoreBaseFilesFound(paths), self.data)
+
+    def findConfigFilePath(self, configfile):
+        """
+        Find the location on disk of configfile and if it exists and was parsed by BitBake
+        emit the ConfigFilePathFound event with the path to the file.
+        """
+        path = bb.cookerdata.findConfigFile(configfile, self.data)
+        if not path:
+            return
+
+        # Generate a list of parsed configuration files by searching the files
+        # listed in the __depends and __base_depends variables with a .conf suffix.
+        conffiles = []
+        dep_files = self.data.getVar('__base_depends', False) or []
+        dep_files = dep_files + (self.data.getVar('__depends', False) or [])
+
+        for f in dep_files:
+            if f[0].endswith(".conf"):
+                conffiles.append(f[0])
+
+        _, conf, conffile = path.rpartition("conf/")
+        match = os.path.join(conf, conffile)
+        # Try and find matches for conf/conffilename.conf as we don't always
+        # have the full path to the file.
+        for cfg in conffiles:
+            if cfg.endswith(match):
+                bb.event.fire(bb.event.ConfigFilePathFound(path),
+                              self.data)
+                break
+
+    def findFilesMatchingInDir(self, filepattern, directory):
+        """
+        Searches for files matching the regex 'pattern' which are children of
+        'directory' in each BBPATH. i.e. to find all rootfs package classes available
+        to BitBake one could call findFilesMatchingInDir(self, 'rootfs_', 'classes')
+        or to find all machine configuration files one could call:
+        findFilesMatchingInDir(self, 'conf/machines', 'conf')
+        """
+
+        matches = []
+        p = re.compile(re.escape(filepattern))
+        bbpaths = self.data.getVar('BBPATH', True).split(':')
+        for path in bbpaths:
+            dirpath = os.path.join(path, directory)
+            if os.path.exists(dirpath):
+                for root, dirs, files in os.walk(dirpath):
+                    for f in files:
+                        if p.search(f):
+                            matches.append(f)
+
+        if matches:
+            bb.event.fire(bb.event.FilesMatchingFound(filepattern, matches), self.data)
+
+    def findConfigFiles(self, varname):
+        """
+        Find config files which are appropriate values for varname.
+        i.e. MACHINE, DISTRO
+        """
+        possible = []
+        var = varname.lower()
+
+        data = self.data
+        # iterate configs
+        bbpaths = data.getVar('BBPATH', True).split(':')
+        for path in bbpaths:
+            confpath = os.path.join(path, "conf", var)
+            if os.path.exists(confpath):
+                for root, dirs, files in os.walk(confpath):
+                    # get all child files, these are appropriate values
+                    for f in files:
+                        val, sep, end = f.rpartition('.')
+                        if end == 'conf':
+                            possible.append(val)
+
+        if possible:
+            bb.event.fire(bb.event.ConfigFilesFound(var, possible), self.data)
+
+    def findInheritsClass(self, klass):
+        """
+        Find all recipes which inherit the specified class
+        """
+        pkg_list = []
+
+        for pfn in self.recipecache.pkg_fn:
+            inherits = self.recipecache.inherits.get(pfn, None)
+            if inherits and inherits.count(klass) > 0:
+                pkg_list.append(self.recipecache.pkg_fn[pfn])
+
+        return pkg_list
+
+    def generateTargetsTree(self, klass=None, pkgs=None):
+        """
+        Generate a dependency tree of buildable targets
+        Generate an event with the result
+        """
+        # if the caller hasn't specified a pkgs list default to universe
+        if not pkgs:
+            pkgs = ['universe']
+        # if inherited_class passed ensure all recipes which inherit the
+        # specified class are included in pkgs
+        if klass:
+            extra_pkgs = self.findInheritsClass(klass)
+            pkgs = pkgs + extra_pkgs
+
+        # generate a dependency tree for all our packages
+        tree = self.generatePkgDepTreeData(pkgs, 'build')
+        bb.event.fire(bb.event.TargetsTreeGenerated(tree), self.data)
+
+    def buildWorldTargetList(self):
+        """
+         Build package list for "bitbake world"
+        """
+        parselog.debug(1, "collating packages for \"world\"")
+        for f in self.recipecache.possible_world:
+            terminal = True
+            pn = self.recipecache.pkg_fn[f]
+
+            for p in self.recipecache.pn_provides[pn]:
+                if p.startswith('virtual/'):
+                    parselog.debug(2, "World build skipping %s due to %s provider starting with virtual/", f, p)
+                    terminal = False
+                    break
+                for pf in self.recipecache.providers[p]:
+                    if self.recipecache.pkg_fn[pf] != pn:
+                        parselog.debug(2, "World build skipping %s due to both us and %s providing %s", f, pf, p)
+                        terminal = False
+                        break
+            if terminal:
+                self.recipecache.world_target.add(pn)
+
+    def interactiveMode( self ):
+        """Drop off into a shell"""
+        try:
+            from bb import shell
+        except ImportError:
+            parselog.exception("Interactive mode not available")
+            sys.exit(1)
+        else:
+            shell.start( self )
+
+
+    def handleCollections( self, collections ):
+        """Handle collections"""
+        errors = False
+        self.recipecache.bbfile_config_priorities = []
+        if collections:
+            collection_priorities = {}
+            collection_depends = {}
+            collection_list = collections.split()
+            min_prio = 0
+            for c in collection_list:
+                # Get collection priority if defined explicitly
+                priority = self.data.getVar("BBFILE_PRIORITY_%s" % c, True)
+                if priority:
+                    try:
+                        prio = int(priority)
+                    except ValueError:
+                        parselog.error("invalid value for BBFILE_PRIORITY_%s: \"%s\"", c, priority)
+                        errors = True
+                    if min_prio == 0 or prio < min_prio:
+                        min_prio = prio
+                    collection_priorities[c] = prio
+                else:
+                    collection_priorities[c] = None
+
+                # Check dependencies and store information for priority calculation
+                deps = self.data.getVar("LAYERDEPENDS_%s" % c, True)
+                if deps:
+                    try:
+                        deplist = bb.utils.explode_dep_versions2(deps)
+                    except bb.utils.VersionStringException as vse:
+                        bb.fatal('Error parsing LAYERDEPENDS_%s: %s' % (c, str(vse)))
+                    for dep, oplist in deplist.iteritems():
+                        if dep in collection_list:
+                            for opstr in oplist:
+                                layerver = self.data.getVar("LAYERVERSION_%s" % dep, True)
+                                (op, depver) = opstr.split()
+                                if layerver:
+                                    try:
+                                        res = bb.utils.vercmp_string_op(layerver, depver, op)
+                                    except bb.utils.VersionStringException as vse:
+                                        bb.fatal('Error parsing LAYERDEPENDS_%s: %s' % (c, str(vse)))
+                                    if not res:
+                                        parselog.error("Layer '%s' depends on version %s of layer '%s', but version %s is currently enabled in your configuration. Check that you are using the correct matching versions/branches of these two layers.", c, opstr, dep, layerver)
+                                        errors = True
+                                else:
+                                    parselog.error("Layer '%s' depends on version %s of layer '%s', which exists in your configuration but does not specify a version. Check that you are using the correct matching versions/branches of these two layers.", c, opstr, dep)
+                                    errors = True
+                        else:
+                            parselog.error("Layer '%s' depends on layer '%s', but this layer is not enabled in your configuration", c, dep)
+                            errors = True
+                    collection_depends[c] = deplist.keys()
+                else:
+                    collection_depends[c] = []
+
+            # Recursively work out collection priorities based on dependencies
+            def calc_layer_priority(collection):
+                if not collection_priorities[collection]:
+                    max_depprio = min_prio
+                    for dep in collection_depends[collection]:
+                        calc_layer_priority(dep)
+                        depprio = collection_priorities[dep]
+                        if depprio > max_depprio:
+                            max_depprio = depprio
+                    max_depprio += 1
+                    parselog.debug(1, "Calculated priority of layer %s as %d", collection, max_depprio)
+                    collection_priorities[collection] = max_depprio
+
+            # Calculate all layer priorities using calc_layer_priority and store in bbfile_config_priorities
+            for c in collection_list:
+                calc_layer_priority(c)
+                regex = self.data.getVar("BBFILE_PATTERN_%s" % c, True)
+                if regex == None:
+                    parselog.error("BBFILE_PATTERN_%s not defined" % c)
+                    errors = True
+                    continue
+                try:
+                    cre = re.compile(regex)
+                except re.error:
+                    parselog.error("BBFILE_PATTERN_%s \"%s\" is not a valid regular expression", c, regex)
+                    errors = True
+                    continue
+                self.recipecache.bbfile_config_priorities.append((c, regex, cre, collection_priorities[c]))
+        if errors:
+            # We've already printed the actual error(s)
+            raise CollectionError("Errors during parsing layer configuration")
+
+    def buildSetVars(self):
+        """
+        Setup any variables needed before starting a build
+        """
+        t = time.gmtime() 
+        if not self.data.getVar("BUILDNAME", False):
+            self.data.setVar("BUILDNAME", "${DATE}${TIME}")
+        self.data.setVar("BUILDSTART", time.strftime('%m/%d/%Y %H:%M:%S', t))
+        self.data.setVar("DATE", time.strftime('%Y%m%d', t))
+        self.data.setVar("TIME", time.strftime('%H%M%S', t))
+
+    def matchFiles(self, bf):
+        """
+        Find the .bb files which match the expression in 'buildfile'.
+        """
+        if bf.startswith("/") or bf.startswith("../"):
+            bf = os.path.abspath(bf)
+
+        self.collection = CookerCollectFiles(self.recipecache.bbfile_config_priorities)
+        filelist, masked = self.collection.collect_bbfiles(self.data, self.expanded_data)
+        try:
+            os.stat(bf)
+            bf = os.path.abspath(bf)
+            return [bf]
+        except OSError:
+            regexp = re.compile(bf)
+            matches = []
+            for f in filelist:
+                if regexp.search(f) and os.path.isfile(f):
+                    matches.append(f)
+            return matches
+
+    def matchFile(self, buildfile):
+        """
+        Find the .bb file which matches the expression in 'buildfile'.
+        Raise an error if multiple files
+        """
+        matches = self.matchFiles(buildfile)
+        if len(matches) != 1:
+            if matches:
+                msg = "Unable to match '%s' to a specific recipe file - %s matches found:" % (buildfile, len(matches))
+                if matches:
+                    for f in matches:
+                        msg += "\n    %s" % f
+                parselog.error(msg)
+            else:
+                parselog.error("Unable to find any recipe file matching '%s'" % buildfile)
+            raise NoSpecificMatch
+        return matches[0]
+
+    def buildFile(self, buildfile, task):
+        """
+        Build the file matching regexp buildfile
+        """
+
+        # Too many people use -b because they think it's how you normally
+        # specify a target to be built, so show a warning
+        bb.warn("Buildfile specified, dependencies will not be handled. If this is not what you want, do not use -b / --buildfile.")
+
+        # Parse the configuration here. We need to do it explicitly here since
+        # buildFile() doesn't use the cache
+        self.parseConfiguration()
+
+        # If we are told to do the None task then query the default task
+        if (task == None):
+            task = self.configuration.cmd
+
+        fn, cls = bb.cache.Cache.virtualfn2realfn(buildfile)
+        fn = self.matchFile(fn)
+
+        self.buildSetVars()
+
+        infos = bb.cache.Cache.parse(fn, self.collection.get_file_appends(fn), \
+                                     self.data,
+                                     self.caches_array)
+        infos = dict(infos)
+
+        fn = bb.cache.Cache.realfn2virtual(fn, cls)
+        try:
+            info_array = infos[fn]
+        except KeyError:
+            bb.fatal("%s does not exist" % fn)
+
+        if info_array[0].skipped:
+            bb.fatal("%s was skipped: %s" % (fn, info_array[0].skipreason))
+
+        self.recipecache.add_from_recipeinfo(fn, info_array)
+
+        # Tweak some variables
+        item = info_array[0].pn
+        self.recipecache.ignored_dependencies = set()
+        self.recipecache.bbfile_priority[fn] = 1
+
+        # Remove external dependencies
+        self.recipecache.task_deps[fn]['depends'] = {}
+        self.recipecache.deps[fn] = []
+        self.recipecache.rundeps[fn] = []
+        self.recipecache.runrecs[fn] = []
+
+        # Invalidate task for target if force mode active
+        if self.configuration.force:
+            logger.verbose("Invalidate task %s, %s", task, fn)
+            if not task.startswith("do_"):
+                task = "do_%s" % task
+            bb.parse.siggen.invalidate_task(task, self.recipecache, fn)
+
+        # Setup taskdata structure
+        taskdata = bb.taskdata.TaskData(self.configuration.abort)
+        taskdata.add_provider(self.data, self.recipecache, item)
+
+        buildname = self.data.getVar("BUILDNAME", True)
+        bb.event.fire(bb.event.BuildStarted(buildname, [item]), self.expanded_data)
+
+        # Execute the runqueue
+        if not task.startswith("do_"):
+            task = "do_%s" % task
+        runlist = [[item, task]]
+
+        rq = bb.runqueue.RunQueue(self, self.data, self.recipecache, taskdata, runlist)
+
+        def buildFileIdle(server, rq, abort):
+
+            msg = None
+            interrupted = 0
+            if abort or self.state == state.forceshutdown:
+                rq.finish_runqueue(True)
+                msg = "Forced shutdown"
+                interrupted = 2
+            elif self.state == state.shutdown:
+                rq.finish_runqueue(False)
+                msg = "Stopped build"
+                interrupted = 1
+            failures = 0
+            try:
+                retval = rq.execute_runqueue()
+            except runqueue.TaskFailure as exc:
+                failures += len(exc.args)
+                retval = False
+            except SystemExit as exc:
+                self.command.finishAsyncCommand()
+                return False
+
+            if not retval:
+                bb.event.fire(bb.event.BuildCompleted(len(rq.rqdata.runq_fnid), buildname, item, failures, interrupted), self.expanded_data)
+                self.command.finishAsyncCommand(msg)
+                return False
+            if retval is True:
+                return True
+            return retval
+
+        self.configuration.server_register_idlecallback(buildFileIdle, rq)
+
+    def buildTargets(self, targets, task):
+        """
+        Attempt to build the targets specified
+        """
+
+        def buildTargetsIdle(server, rq, abort):
+            msg = None
+            interrupted = 0
+            if abort or self.state == state.forceshutdown:
+                rq.finish_runqueue(True)
+                msg = "Forced shutdown"
+                interrupted = 2
+            elif self.state == state.shutdown:
+                rq.finish_runqueue(False)
+                msg = "Stopped build"
+                interrupted = 1
+            failures = 0
+            try:
+                retval = rq.execute_runqueue()
+            except runqueue.TaskFailure as exc:
+                failures += len(exc.args)
+                retval = False
+            except SystemExit as exc:
+                self.command.finishAsyncCommand()
+                return False
+
+            if not retval:
+                bb.event.fire(bb.event.BuildCompleted(len(rq.rqdata.runq_fnid), buildname, targets, failures, interrupted), self.data)
+                self.command.finishAsyncCommand(msg)
+                return False
+            if retval is True:
+                return True
+            return retval
+
+        build.reset_cache()
+        self.buildSetVars()
+
+        taskdata, runlist, fulltargetlist = self.buildTaskData(targets, task, self.configuration.abort)
+
+        buildname = self.data.getVar("BUILDNAME", False)
+        bb.event.fire(bb.event.BuildStarted(buildname, fulltargetlist), self.data)
+
+        rq = bb.runqueue.RunQueue(self, self.data, self.recipecache, taskdata, runlist)
+        if 'universe' in targets:
+            rq.rqdata.warn_multi_bb = True
+
+        self.configuration.server_register_idlecallback(buildTargetsIdle, rq)
+
+
+    def getAllKeysWithFlags(self, flaglist):
+        dump = {}
+        for k in self.data.keys():
+            try:
+                v = self.data.getVar(k, True)
+                if not k.startswith("__") and not isinstance(v, bb.data_smart.DataSmart):
+                    dump[k] = {
+    'v' : v ,
+    'history' : self.data.varhistory.variable(k),
+                    }
+                    for d in flaglist:
+                        dump[k][d] = self.data.getVarFlag(k, d)
+            except Exception as e:
+                print(e)
+        return dump
+
+
+    def generateNewImage(self, image, base_image, package_queue, timestamp, description):
+        '''
+        Create a new image with a "require"/"inherit" base_image statement
+        '''
+        if timestamp:
+            image_name = os.path.splitext(image)[0]
+            timestr = time.strftime("-%Y%m%d-%H%M%S")
+            dest = image_name + str(timestr) + ".bb"
+        else:
+            if not image.endswith(".bb"):
+                dest = image + ".bb"
+            else:
+                dest = image
+
+        basename = False
+        if base_image:
+            with open(base_image, 'r') as f:
+                require_line = f.readline()
+                p = re.compile("IMAGE_BASENAME *=")
+                for line in f:
+                    if p.search(line):
+                        basename = True
+
+        with open(dest, "w") as imagefile:
+            if base_image is None:
+                imagefile.write("inherit core-image\n")
+            else:
+                topdir = self.data.getVar("TOPDIR", False)
+                if topdir in base_image:
+                    base_image = require_line.split()[1]
+                imagefile.write("require " + base_image + "\n")
+            image_install = "IMAGE_INSTALL = \""
+            for package in package_queue:
+                image_install += str(package) + " "
+            image_install += "\"\n"
+            imagefile.write(image_install)
+
+            description_var = "DESCRIPTION = \"" + description + "\"\n"
+            imagefile.write(description_var)
+
+            if basename:
+                # If this is overwritten in a inherited image, reset it to default
+                image_basename = "IMAGE_BASENAME = \"${PN}\"\n"
+                imagefile.write(image_basename)
+
+        self.state = state.initial
+        if timestamp:
+            return timestr
+
+    def updateCacheSync(self):
+        if self.state == state.running:
+            return
+
+        # reload files for which we got notifications
+        for p in self.inotify_modified_files:
+            bb.parse.update_cache(p)
+        self.inotify_modified_files = []
+
+        if not self.baseconfig_valid:
+            logger.debug(1, "Reloading base configuration data")
+            self.initConfigurationData()
+            self.baseconfig_valid = True
+            self.parsecache_valid = False
+
+    # This is called for all async commands when self.state != running
+    def updateCache(self):
+        if self.state == state.running:
+            return
+
+        if self.state in (state.shutdown, state.forceshutdown, state.error):
+            if hasattr(self.parser, 'shutdown'):
+                self.parser.shutdown(clean=False, force = True)
+            raise bb.BBHandledException()
+
+        if self.state != state.parsing:
+            self.updateCacheSync()
+
+        if self.state != state.parsing and not self.parsecache_valid:
+            self.parseConfiguration ()
+            if CookerFeatures.SEND_SANITYEVENTS in self.featureset:
+                bb.event.fire(bb.event.SanityCheck(False), self.data)
+
+            ignore = self.expanded_data.getVar("ASSUME_PROVIDED", True) or ""
+            self.recipecache.ignored_dependencies = set(ignore.split())
+
+            for dep in self.configuration.extra_assume_provided:
+                self.recipecache.ignored_dependencies.add(dep)
+
+            self.collection = CookerCollectFiles(self.recipecache.bbfile_config_priorities)
+            (filelist, masked) = self.collection.collect_bbfiles(self.data, self.expanded_data)
+
+            self.parser = CookerParser(self, filelist, masked)
+            self.parsecache_valid = True
+
+        self.state = state.parsing
+
+        if not self.parser.parse_next():
+            collectlog.debug(1, "parsing complete")
+            if self.parser.error:
+                raise bb.BBHandledException()
+            self.show_appends_with_no_recipes()
+            self.handlePrefProviders()
+            self.recipecache.bbfile_priority = self.collection.collection_priorities(self.recipecache.pkg_fn, self.data)
+            self.state = state.running
+
+            # Send an event listing all stamps reachable after parsing
+            # which the metadata may use to clean up stale data
+            event = bb.event.ReachableStamps(self.recipecache.stamp)
+            bb.event.fire(event, self.expanded_data)
+            return None
+
+        return True
+
+    def checkPackages(self, pkgs_to_build):
+
+        # Return a copy, don't modify the original
+        pkgs_to_build = pkgs_to_build[:]
+
+        if len(pkgs_to_build) == 0:
+            raise NothingToBuild
+
+        ignore = (self.expanded_data.getVar("ASSUME_PROVIDED", True) or "").split()
+        for pkg in pkgs_to_build:
+            if pkg in ignore:
+                parselog.warn("Explicit target \"%s\" is in ASSUME_PROVIDED, ignoring" % pkg)
+
+        if 'world' in pkgs_to_build:
+            self.buildWorldTargetList()
+            pkgs_to_build.remove('world')
+            for t in self.recipecache.world_target:
+                pkgs_to_build.append(t)
+
+        if 'universe' in pkgs_to_build:
+            parselog.warn("The \"universe\" target is only intended for testing and may produce errors.")
+            parselog.debug(1, "collating packages for \"universe\"")
+            pkgs_to_build.remove('universe')
+            for t in self.recipecache.universe_target:
+                pkgs_to_build.append(t)
+
+        return pkgs_to_build
+
+
+
+
+    def pre_serve(self):
+        # Empty the environment. The environment will be populated as
+        # necessary from the data store.
+        #bb.utils.empty_environment()
+        try:
+            self.prhost = prserv.serv.auto_start(self.data)
+        except prserv.serv.PRServiceConfigError:
+            bb.event.fire(CookerExit(), self.expanded_data)
+            self.state = state.error
+        return
+
+    def post_serve(self):
+        prserv.serv.auto_shutdown(self.data)
+        bb.event.fire(CookerExit(), self.expanded_data)
+        lockfile = self.lock.name
+        self.lock.close()
+        self.lock = None
+
+        while not self.lock:
+            with bb.utils.timeout(3):
+                self.lock = bb.utils.lockfile(lockfile, shared=False, retry=False, block=True)
+                if not self.lock:
+                    # Some systems may not have lsof available
+                    procs = None
+                    try:
+                        procs = subprocess.check_output(["lsof", '-w', lockfile], stderr=subprocess.STDOUT)
+                    except OSError as e:
+                        if e.errno != errno.ENOENT:
+                            raise
+                    if procs is None:
+                        # Fall back to fuser if lsof is unavailable
+                        try:
+                            procs = subprocess.check_output(["fuser", '-v', lockfile], stderr=subprocess.STDOUT)
+                        except OSError as e:
+                            if e.errno != errno.ENOENT:
+                                raise
+
+                    msg = "Delaying shutdown due to active processes which appear to be holding bitbake.lock"
+                    if procs:
+                        msg += ":\n%s" % str(procs)
+                    print(msg)
+
+
+    def shutdown(self, force = False):
+        if force:
+            self.state = state.forceshutdown
+        else:
+            self.state = state.shutdown
+
+    def finishcommand(self):
+        self.state = state.initial
+
+    def reset(self):
+        self.initConfigurationData()
+
+    def lockBitbake(self):
+        if not hasattr(self, 'lock'):
+            self.lock = None
+            if self.data:
+                lockfile = self.data.expand("${TOPDIR}/bitbake.lock")
+                if lockfile:
+                    self.lock = bb.utils.lockfile(lockfile, False, False)
+        return self.lock
+
+    def unlockBitbake(self):
+        if hasattr(self, 'lock') and self.lock:
+            bb.utils.unlockfile(self.lock)
+
+def server_main(cooker, func, *args):
+    cooker.pre_serve()
+
+    if cooker.configuration.profile:
+        try:
+            import cProfile as profile
+        except:
+            import profile
+        prof = profile.Profile()
+
+        ret = profile.Profile.runcall(prof, func, *args)
+
+        prof.dump_stats("profile.log")
+        bb.utils.process_profilelog("profile.log")
+        print("Raw profiling information saved to profile.log and processed statistics to profile.log.processed")
+
+    else:
+        ret = func(*args)
+
+    cooker.post_serve()
+
+    return ret
+
+class CookerExit(bb.event.Event):
+    """
+    Notify clients of the Cooker shutdown
+    """
+
+    def __init__(self):
+        bb.event.Event.__init__(self)
+
+
+class CookerCollectFiles(object):
+    def __init__(self, priorities):
+        self.bbappends = []
+        self.bbfile_config_priorities = priorities
+
+    def calc_bbfile_priority( self, filename, matched = None ):
+        for _, _, regex, pri in self.bbfile_config_priorities:
+            if regex.match(filename):
+                if matched != None:
+                    if not regex in matched:
+                        matched.add(regex)
+                return pri
+        return 0
+
+    def get_bbfiles(self):
+        """Get list of default .bb files by reading out the current directory"""
+        path = os.getcwd()
+        contents = os.listdir(path)
+        bbfiles = []
+        for f in contents:
+            if f.endswith(".bb"):
+                bbfiles.append(os.path.abspath(os.path.join(path, f)))
+        return bbfiles
+
+    def find_bbfiles(self, path):
+        """Find all the .bb and .bbappend files in a directory"""
+        found = []
+        for dir, dirs, files in os.walk(path):
+            for ignored in ('SCCS', 'CVS', '.svn'):
+                if ignored in dirs:
+                    dirs.remove(ignored)
+            found += [os.path.join(dir, f) for f in files if (f.endswith(['.bb', '.bbappend']))]
+
+        return found
+
+    def collect_bbfiles(self, config, eventdata):
+        """Collect all available .bb build files"""
+        masked = 0
+
+        collectlog.debug(1, "collecting .bb files")
+
+        files = (config.getVar( "BBFILES", True) or "").split()
+        config.setVar("BBFILES", " ".join(files))
+
+        # Sort files by priority
+        files.sort( key=lambda fileitem: self.calc_bbfile_priority(fileitem) )
+
+        if not len(files):
+            files = self.get_bbfiles()
+
+        if not len(files):
+            collectlog.error("no recipe files to build, check your BBPATH and BBFILES?")
+            bb.event.fire(CookerExit(), eventdata)
+
+        # Can't use set here as order is important
+        newfiles = []
+        for f in files:
+            if os.path.isdir(f):
+                dirfiles = self.find_bbfiles(f)
+                for g in dirfiles:
+                    if g not in newfiles:
+                        newfiles.append(g)
+            else:
+                globbed = glob.glob(f)
+                if not globbed and os.path.exists(f):
+                    globbed = [f]
+                for g in globbed:
+                    if g not in newfiles:
+                        newfiles.append(g)
+
+        bbmask = config.getVar('BBMASK', True)
+
+        if bbmask:
+            try:
+                bbmask_compiled = re.compile(bbmask)
+            except sre_constants.error:
+                collectlog.critical("BBMASK is not a valid regular expression, ignoring.")
+                return list(newfiles), 0
+
+        bbfiles = []
+        bbappend = []
+        for f in newfiles:
+            if bbmask and bbmask_compiled.search(f):
+                collectlog.debug(1, "skipping masked file %s", f)
+                masked += 1
+                continue
+            if f.endswith('.bb'):
+                bbfiles.append(f)
+            elif f.endswith('.bbappend'):
+                bbappend.append(f)
+            else:
+                collectlog.debug(1, "skipping %s: unknown file extension", f)
+
+        # Build a list of .bbappend files for each .bb file
+        for f in bbappend:
+            base = os.path.basename(f).replace('.bbappend', '.bb')
+            self.bbappends.append((base, f))
+
+        # Find overlayed recipes
+        # bbfiles will be in priority order which makes this easy
+        bbfile_seen = dict()
+        self.overlayed = defaultdict(list)
+        for f in reversed(bbfiles):
+            base = os.path.basename(f)
+            if base not in bbfile_seen:
+                bbfile_seen[base] = f
+            else:
+                topfile = bbfile_seen[base]
+                self.overlayed[topfile].append(f)
+
+        return (bbfiles, masked)
+
+    def get_file_appends(self, fn):
+        """
+        Returns a list of .bbappend files to apply to fn
+        """
+        filelist = []
+        f = os.path.basename(fn)
+        for b in self.bbappends:
+            (bbappend, filename) = b
+            if (bbappend == f) or ('%' in bbappend and bbappend.startswith(f[:bbappend.index('%')])):
+                filelist.append(filename)
+        return filelist
+
+    def collection_priorities(self, pkgfns, d):
+
+        priorities = {}
+
+        # Calculate priorities for each file
+        matched = set()
+        for p in pkgfns:
+            realfn, cls = bb.cache.Cache.virtualfn2realfn(p)
+            priorities[p] = self.calc_bbfile_priority(realfn, matched)
+
+        # Don't show the warning if the BBFILE_PATTERN did match .bbappend files
+        unmatched = set()
+        for _, _, regex, pri in self.bbfile_config_priorities:
+            if not regex in matched:
+                unmatched.add(regex)
+
+        def findmatch(regex):
+            for b in self.bbappends:
+                (bbfile, append) = b
+                if regex.match(append):
+                    return True
+            return False
+
+        for unmatch in unmatched.copy():
+            if findmatch(unmatch):
+                unmatched.remove(unmatch)
+
+        for collection, pattern, regex, _ in self.bbfile_config_priorities:
+            if regex in unmatched:
+                if d.getVar('BBFILE_PATTERN_IGNORE_EMPTY_%s' % collection, True) != '1':
+                    collectlog.warn("No bb files matched BBFILE_PATTERN_%s '%s'" % (collection, pattern))
+
+        return priorities
+
+class ParsingFailure(Exception):
+    def __init__(self, realexception, recipe):
+        self.realexception = realexception
+        self.recipe = recipe
+        Exception.__init__(self, realexception, recipe)
+
+class Feeder(multiprocessing.Process):
+    def __init__(self, jobs, to_parsers, quit):
+        self.quit = quit
+        self.jobs = jobs
+        self.to_parsers = to_parsers
+        multiprocessing.Process.__init__(self)
+
+    def run(self):
+        while True:
+            try:
+                quit = self.quit.get_nowait()
+            except Queue.Empty:
+                pass
+            else:
+                if quit == 'cancel':
+                    self.to_parsers.cancel_join_thread()
+                break
+
+            try:
+                job = self.jobs.pop()
+            except IndexError:
+                break
+
+            try:
+                self.to_parsers.put(job, timeout=0.5)
+            except Queue.Full:
+                self.jobs.insert(0, job)
+                continue
+
+class Parser(multiprocessing.Process):
+    def __init__(self, jobs, results, quit, init, profile):
+        self.jobs = jobs
+        self.results = results
+        self.quit = quit
+        self.init = init
+        multiprocessing.Process.__init__(self)
+        self.context = bb.utils.get_context().copy()
+        self.handlers = bb.event.get_class_handlers().copy()
+        self.profile = profile
+
+    def run(self):
+
+        if not self.profile:
+            self.realrun()
+            return
+
+        try:
+            import cProfile as profile
+        except:
+            import profile
+        prof = profile.Profile()
+        try:
+            profile.Profile.runcall(prof, self.realrun)
+        finally:
+            logfile = "profile-parse-%s.log" % multiprocessing.current_process().name
+            prof.dump_stats(logfile)
+
+    def realrun(self):
+        if self.init:
+            self.init()
+
+        pending = []
+        while True:
+            try:
+                self.quit.get_nowait()
+            except Queue.Empty:
+                pass
+            else:
+                self.results.cancel_join_thread()
+                break
+
+            if pending:
+                result = pending.pop()
+            else:
+                try:
+                    job = self.jobs.get(timeout=0.25)
+                except Queue.Empty:
+                    continue
+
+                if job is None:
+                    break
+                result = self.parse(*job)
+
+            try:
+                self.results.put(result, timeout=0.25)
+            except Queue.Full:
+                pending.append(result)
+
+    def parse(self, filename, appends, caches_array):
+        try:
+            # Reset our environment and handlers to the original settings
+            bb.utils.set_context(self.context.copy())
+            bb.event.set_class_handlers(self.handlers.copy())
+            return True, bb.cache.Cache.parse(filename, appends, self.cfg, caches_array)
+        except Exception as exc:
+            tb = sys.exc_info()[2]
+            exc.recipe = filename
+            exc.traceback = list(bb.exceptions.extract_traceback(tb, context=3))
+            return True, 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
+        # a SystemExit event for example.
+        except BaseException as exc:
+            return True, ParsingFailure(exc, filename)
+
+class CookerParser(object):
+    def __init__(self, cooker, filelist, masked):
+        self.filelist = filelist
+        self.cooker = cooker
+        self.cfgdata = cooker.data
+        self.cfghash = cooker.data_hash
+
+        # Accounting statistics
+        self.parsed = 0
+        self.cached = 0
+        self.error = 0
+        self.masked = masked
+
+        self.skipped = 0
+        self.virtuals = 0
+        self.total = len(filelist)
+
+        self.current = 0
+        self.num_processes = int(self.cfgdata.getVar("BB_NUMBER_PARSE_THREADS", True) or
+                                 multiprocessing.cpu_count())
+        self.process_names = []
+
+        self.bb_cache = bb.cache.Cache(self.cfgdata, self.cfghash, cooker.caches_array)
+        self.fromcache = []
+        self.willparse = []
+        for filename in self.filelist:
+            appends = self.cooker.collection.get_file_appends(filename)
+            if not self.bb_cache.cacheValid(filename, appends):
+                self.willparse.append((filename, appends, cooker.caches_array))
+            else:
+                self.fromcache.append((filename, appends))
+        self.toparse = self.total - len(self.fromcache)
+        self.progress_chunk = max(self.toparse / 100, 1)
+
+        self.start()
+        self.haveshutdown = False
+
+    def start(self):
+        self.results = self.load_cached()
+        self.processes = []
+        if self.toparse:
+            bb.event.fire(bb.event.ParseStarted(self.toparse), self.cfgdata)
+            def init():
+                Parser.cfg = self.cfgdata
+                multiprocessing.util.Finalize(None, bb.codeparser.parser_cache_save, args=(self.cfgdata,), exitpriority=1)
+                multiprocessing.util.Finalize(None, bb.fetch.fetcher_parse_save, args=(self.cfgdata,), exitpriority=1)
+
+            self.feeder_quit = multiprocessing.Queue(maxsize=1)
+            self.parser_quit = multiprocessing.Queue(maxsize=self.num_processes)
+            self.jobs = multiprocessing.Queue(maxsize=self.num_processes)
+            self.result_queue = multiprocessing.Queue()
+            self.feeder = Feeder(self.willparse, self.jobs, self.feeder_quit)
+            self.feeder.start()
+            for i in range(0, self.num_processes):
+                parser = Parser(self.jobs, self.result_queue, self.parser_quit, init, self.cooker.configuration.profile)
+                parser.start()
+                self.process_names.append(parser.name)
+                self.processes.append(parser)
+
+            self.results = itertools.chain(self.results, self.parse_generator())
+
+    def shutdown(self, clean=True, force=False):
+        if not self.toparse:
+            return
+        if self.haveshutdown:
+            return
+        self.haveshutdown = True
+
+        if clean:
+            event = bb.event.ParseCompleted(self.cached, self.parsed,
+                                            self.skipped, self.masked,
+                                            self.virtuals, self.error,
+                                            self.total)
+
+            bb.event.fire(event, self.cfgdata)
+            self.feeder_quit.put(None)
+            for process in self.processes:
+                self.jobs.put(None)
+        else:
+            self.feeder_quit.put('cancel')
+
+            self.parser_quit.cancel_join_thread()
+            for process in self.processes:
+                self.parser_quit.put(None)
+
+            self.jobs.cancel_join_thread()
+
+        for process in self.processes:
+            if force:
+                process.join(.1)
+                process.terminate()
+            else:
+                process.join()
+        self.feeder.join()
+
+        sync = threading.Thread(target=self.bb_cache.sync)
+        sync.start()
+        multiprocessing.util.Finalize(None, sync.join, exitpriority=-100)
+        bb.codeparser.parser_cache_savemerge(self.cooker.data)
+        bb.fetch.fetcher_parse_done(self.cooker.data)
+        if self.cooker.configuration.profile:
+            profiles = []
+            for i in self.process_names:
+                logfile = "profile-parse-%s.log" % i
+                if os.path.exists(logfile):
+                    profiles.append(logfile)
+
+            pout = "profile-parse.log.processed"
+            bb.utils.process_profilelog(profiles, pout = pout)
+            print("Processed parsing statistics saved to %s" % (pout))
+
+    def load_cached(self):
+        for filename, appends in self.fromcache:
+            cached, infos = self.bb_cache.load(filename, appends, self.cfgdata)
+            yield not cached, infos
+
+    def parse_generator(self):
+        while True:
+            if self.parsed >= self.toparse:
+                break
+
+            try:
+                result = self.result_queue.get(timeout=0.25)
+            except Queue.Empty:
+                pass
+            else:
+                value = result[1]
+                if isinstance(value, BaseException):
+                    raise value
+                else:
+                    yield result
+
+    def parse_next(self):
+        result = []
+        parsed = None
+        try:
+            parsed, result = self.results.next()
+        except StopIteration:
+            self.shutdown()
+            return False
+        except bb.BBHandledException as exc:
+            self.error += 1
+            logger.error('Failed to parse recipe: %s' % exc.recipe)
+            self.shutdown(clean=False)
+            return False
+        except ParsingFailure as exc:
+            self.error += 1
+            logger.error('Unable to parse %s: %s' %
+                     (exc.recipe, bb.exceptions.to_string(exc.realexception)))
+            self.shutdown(clean=False)
+            return False
+        except bb.parse.ParseError as exc:
+            self.error += 1
+            logger.error(str(exc))
+            self.shutdown(clean=False)
+            return False
+        except bb.data_smart.ExpansionError as exc:
+            self.error += 1
+            _, value, _ = sys.exc_info()
+            logger.error('ExpansionError during parsing %s: %s', value.recipe, str(exc))
+            self.shutdown(clean=False)
+            return False
+        except SyntaxError as exc:
+            self.error += 1
+            logger.error('Unable to parse %s', exc.recipe)
+            self.shutdown(clean=False)
+            return False
+        except Exception as exc:
+            self.error += 1
+            etype, value, tb = sys.exc_info()
+            if hasattr(value, "recipe"):
+                logger.error('Unable to parse %s', value.recipe,
+                            exc_info=(etype, value, exc.traceback))
+            else:
+                # Most likely, an exception occurred during raising an exception
+                import traceback
+                logger.error('Exception during parse: %s' % traceback.format_exc())
+            self.shutdown(clean=False)
+            return False
+
+        self.current += 1
+        self.virtuals += len(result)
+        if parsed:
+            self.parsed += 1
+            if self.parsed % self.progress_chunk == 0:
+                bb.event.fire(bb.event.ParseProgress(self.parsed, self.toparse),
+                              self.cfgdata)
+        else:
+            self.cached += 1
+
+        for virtualfn, info_array in result:
+            if info_array[0].skipped:
+                self.skipped += 1
+                self.cooker.skiplist[virtualfn] = SkippedPackage(info_array[0])
+            self.bb_cache.add_info(virtualfn, info_array, self.cooker.recipecache,
+                                        parsed=parsed, watcher = self.cooker.add_filewatch)
+        return True
+
+    def reparse(self, filename):
+        infos = self.bb_cache.parse(filename,
+                                    self.cooker.collection.get_file_appends(filename),
+                                    self.cfgdata, self.cooker.caches_array)
+        for vfn, info_array in infos:
+            self.cooker.recipecache.add_from_recipeinfo(vfn, info_array)
diff --git a/bitbake/lib/bb/cookerdata.py b/bitbake/lib/bb/cookerdata.py
new file mode 100644
index 0000000..f19c283
--- /dev/null
+++ b/bitbake/lib/bb/cookerdata.py
@@ -0,0 +1,336 @@
+#!/usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Copyright (C) 2003, 2004  Chris Larson
+# Copyright (C) 2003, 2004  Phil Blundell
+# Copyright (C) 2003 - 2005 Michael 'Mickey' Lauer
+# Copyright (C) 2005        Holger Hans Peter Freyther
+# Copyright (C) 2005        ROAD GmbH
+# Copyright (C) 2006        Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import os, sys
+from functools import wraps
+import logging
+import bb
+from bb import data
+import bb.parse
+
+logger      = logging.getLogger("BitBake")
+parselog    = logging.getLogger("BitBake.Parsing")
+
+class ConfigParameters(object):
+    def __init__(self, argv=sys.argv):
+        self.options, targets = self.parseCommandLine(argv)
+        self.environment = self.parseEnvironment()
+
+        self.options.pkgs_to_build = targets or []
+
+        self.options.tracking = False
+        if hasattr(self.options, "show_environment") and self.options.show_environment:
+            self.options.tracking = True
+
+        for key, val in self.options.__dict__.items():
+            setattr(self, key, val)
+
+    def parseCommandLine(self, argv=sys.argv):
+        raise Exception("Caller must implement commandline option parsing")
+
+    def parseEnvironment(self):
+        return os.environ.copy()
+
+    def updateFromServer(self, server):
+        if not self.options.cmd:
+            defaulttask, error = server.runCommand(["getVariable", "BB_DEFAULT_TASK"])
+            if error:
+                raise Exception("Unable to get the value of BB_DEFAULT_TASK from the server: %s" % error)
+            self.options.cmd = defaulttask or "build"
+        _, error = server.runCommand(["setConfig", "cmd", self.options.cmd])
+        if error:
+            raise Exception("Unable to set configuration option 'cmd' on the server: %s" % error)
+
+        if not self.options.pkgs_to_build:
+            bbpkgs, error = server.runCommand(["getVariable", "BBPKGS"])
+            if error:
+                raise Exception("Unable to get the value of BBPKGS from the server: %s" % error)
+            if bbpkgs:
+                self.options.pkgs_to_build.extend(bbpkgs.split())
+
+    def updateToServer(self, server, environment):
+        options = {}
+        for o in ["abort", "tryaltconfigs", "force", "invalidate_stamp", 
+                  "verbose", "debug", "dry_run", "dump_signatures", 
+                  "debug_domains", "extra_assume_provided", "profile",
+                  "prefile", "postfile"]:
+            options[o] = getattr(self.options, o)
+
+        ret, error = server.runCommand(["updateConfig", options, environment])
+        if error:
+                raise Exception("Unable to update the server configuration with local parameters: %s" % error)
+
+    def parseActions(self):
+        # Parse any commandline into actions
+        action = {'action':None, 'msg':None}
+        if self.options.show_environment:
+            if 'world' in self.options.pkgs_to_build:
+                action['msg'] = "'world' is not a valid target for --environment."
+            elif 'universe' in self.options.pkgs_to_build:
+                action['msg'] = "'universe' is not a valid target for --environment."
+            elif len(self.options.pkgs_to_build) > 1:
+                action['msg'] = "Only one target can be used with the --environment option."
+            elif self.options.buildfile and len(self.options.pkgs_to_build) > 0:
+                action['msg'] = "No target should be used with the --environment and --buildfile options."
+            elif len(self.options.pkgs_to_build) > 0:
+                action['action'] = ["showEnvironmentTarget", self.options.pkgs_to_build]
+            else:
+                action['action'] = ["showEnvironment", self.options.buildfile]
+        elif self.options.buildfile is not None:
+            action['action'] = ["buildFile", self.options.buildfile, self.options.cmd]
+        elif self.options.revisions_changed:
+            action['action'] = ["compareRevisions"]
+        elif self.options.show_versions:
+            action['action'] = ["showVersions"]
+        elif self.options.parse_only:
+            action['action'] = ["parseFiles"]
+        elif self.options.dot_graph:
+            if self.options.pkgs_to_build:
+                action['action'] = ["generateDotGraph", self.options.pkgs_to_build, self.options.cmd]
+            else:
+                action['msg'] = "Please specify a package name for dependency graph generation."
+        else:
+            if self.options.pkgs_to_build:
+                action['action'] = ["buildTargets", self.options.pkgs_to_build, self.options.cmd]
+            else:
+                #action['msg'] = "Nothing to do.  Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information."
+                action = None
+        self.options.initialaction = action
+        return action
+
+class CookerConfiguration(object):
+    """
+    Manages build options and configurations for one run
+    """
+
+    def __init__(self):
+        self.debug_domains = []
+        self.extra_assume_provided = []
+        self.prefile = []
+        self.postfile = []
+        self.debug = 0
+        self.cmd = None
+        self.abort = True
+        self.force = False
+        self.profile = False
+        self.nosetscene = False
+        self.invalidate_stamp = False
+        self.dump_signatures = []
+        self.dry_run = False
+        self.tracking = False
+        self.interface = []
+        self.writeeventlog = False
+
+        self.env = {}
+
+    def setConfigParameters(self, parameters):
+        for key in self.__dict__.keys():
+            if key in parameters.options.__dict__:
+                setattr(self, key, parameters.options.__dict__[key])
+        self.env = parameters.environment.copy()
+        self.tracking = parameters.tracking
+
+    def setServerRegIdleCallback(self, srcb):
+        self.server_register_idlecallback = srcb
+
+    def __getstate__(self):
+        state = {}
+        for key in self.__dict__.keys():
+            if key == "server_register_idlecallback":
+                state[key] = None
+            else:
+                state[key] = getattr(self, key)
+        return state
+
+    def __setstate__(self,state):
+        for k in state:
+            setattr(self, k, state[k]) 
+
+
+def catch_parse_error(func):
+    """Exception handling bits for our parsing"""
+    @wraps(func)
+    def wrapped(fn, *args):
+        try:
+            return func(fn, *args)
+        except IOError as exc:
+            import traceback
+            parselog.critical(traceback.format_exc())
+            parselog.critical("Unable to parse %s: %s" % (fn, exc))
+            sys.exit(1)
+        except (bb.parse.ParseError, bb.data_smart.ExpansionError) as exc:
+            import traceback
+
+            bbdir = os.path.dirname(__file__) + os.sep
+            exc_class, exc, tb = sys.exc_info()
+            for tb in iter(lambda: tb.tb_next, None):
+                # Skip frames in bitbake itself, we only want the metadata
+                fn, _, _, _ = traceback.extract_tb(tb, 1)[0]
+                if not fn.startswith(bbdir):
+                    break
+            parselog.critical("Unable to parse %s", fn, exc_info=(exc_class, exc, tb))
+            sys.exit(1)
+    return wrapped
+
+@catch_parse_error
+def parse_config_file(fn, data, include=True):
+    return bb.parse.handle(fn, data, include)
+
+@catch_parse_error
+def _inherit(bbclass, data):
+    bb.parse.BBHandler.inherit(bbclass, "configuration INHERITs", 0, data)
+    return data
+
+def findConfigFile(configfile, data):
+    search = []
+    bbpath = data.getVar("BBPATH", True)
+    if bbpath:
+        for i in bbpath.split(":"):
+            search.append(os.path.join(i, "conf", configfile))
+    path = os.getcwd()
+    while path != "/":
+        search.append(os.path.join(path, "conf", configfile))
+        path, _ = os.path.split(path)
+
+    for i in search:
+        if os.path.exists(i):
+            return i
+
+    return None
+
+class CookerDataBuilder(object):
+
+    def __init__(self, cookercfg, worker = False):
+
+        self.prefiles = cookercfg.prefile
+        self.postfiles = cookercfg.postfile
+        self.tracking = cookercfg.tracking
+
+        bb.utils.set_context(bb.utils.clean_context())
+        bb.event.set_class_handlers(bb.event.clean_class_handlers())
+        self.data = bb.data.init()
+        if self.tracking:
+            self.data.enableTracking()
+
+        # Keep a datastore of the initial environment variables and their
+        # values from when BitBake was launched to enable child processes
+        # to use environment variables which have been cleaned from the
+        # BitBake processes env
+        self.savedenv = bb.data.init()
+        for k in cookercfg.env:
+            self.savedenv.setVar(k, cookercfg.env[k])
+
+        filtered_keys = bb.utils.approved_variables()
+        bb.data.inheritFromOS(self.data, self.savedenv, filtered_keys)
+        self.data.setVar("BB_ORIGENV", self.savedenv)
+        
+        if worker:
+            self.data.setVar("BB_WORKERCONTEXT", "1")
+
+    def parseBaseConfiguration(self):
+        try:
+            self.parseConfigurationFiles(self.prefiles, self.postfiles)
+        except SyntaxError:
+            raise bb.BBHandledException
+        except bb.data_smart.ExpansionError as e:
+            logger.error(str(e))
+            raise bb.BBHandledException
+        except Exception:
+            logger.exception("Error parsing configuration files")
+            raise bb.BBHandledException
+
+    def _findLayerConf(self, data):
+        return findConfigFile("bblayers.conf", data)
+
+    def parseConfigurationFiles(self, prefiles, postfiles):
+        data = self.data
+        bb.parse.init_parser(data)
+
+        # Parse files for loading *before* bitbake.conf and any includes
+        for f in prefiles:
+            data = parse_config_file(f, data)
+
+        layerconf = self._findLayerConf(data)
+        if layerconf:
+            parselog.debug(2, "Found bblayers.conf (%s)", layerconf)
+            # By definition bblayers.conf is in conf/ of TOPDIR.
+            # We may have been called with cwd somewhere else so reset TOPDIR
+            data.setVar("TOPDIR", os.path.dirname(os.path.dirname(layerconf)))
+            data = parse_config_file(layerconf, data)
+
+            layers = (data.getVar('BBLAYERS', True) or "").split()
+
+            data = bb.data.createCopy(data)
+            approved = bb.utils.approved_variables()
+            for layer in layers:
+                parselog.debug(2, "Adding layer %s", layer)
+                if 'HOME' in approved and '~' in layer:
+                    layer = os.path.expanduser(layer)
+                data.setVar('LAYERDIR', layer)
+                data = parse_config_file(os.path.join(layer, "conf", "layer.conf"), data)
+                data.expandVarref('LAYERDIR')
+
+            data.delVar('LAYERDIR')
+
+        if not data.getVar("BBPATH", True):
+            msg = "The BBPATH variable is not set"
+            if not layerconf:
+                msg += (" and bitbake did not find a conf/bblayers.conf file in"
+                        " the expected location.\nMaybe you accidentally"
+                        " invoked bitbake from the wrong directory?")
+            raise SystemExit(msg)
+
+        data = parse_config_file(os.path.join("conf", "bitbake.conf"), data)
+
+        # Parse files for loading *after* bitbake.conf and any includes
+        for p in postfiles:
+            data = parse_config_file(p, data)
+
+        # Handle any INHERITs and inherit the base class
+        bbclasses  = ["base"] + (data.getVar('INHERIT', True) or "").split()
+        for bbclass in bbclasses:
+            data = _inherit(bbclass, data)
+
+        # Nomally we only register event handlers at the end of parsing .bb files
+        # We register any handlers we've found so far here...
+        for var in data.getVar('__BBHANDLERS', False) or []:
+            bb.event.register(var, data.getVar(var, False),  (data.getVarFlag(var, "eventmask", True) or "").split())
+
+        if data.getVar("BB_WORKERCONTEXT", False) is None:
+            bb.fetch.fetcher_init(data)
+        bb.codeparser.parser_cache_init(data)
+        bb.event.fire(bb.event.ConfigParsed(), data)
+
+        if data.getVar("BB_INVALIDCONF", False) is True:
+            data.setVar("BB_INVALIDCONF", False)
+            self.parseConfigurationFiles(self.prefiles, self.postfiles)
+            return
+
+        bb.parse.init_parser(data)
+        data.setVar('BBINCLUDED',bb.parse.get_file_depends(data))
+        self.data = data
+        self.data_hash = data.get_hash()
+
+
+
diff --git a/bitbake/lib/bb/daemonize.py b/bitbake/lib/bb/daemonize.py
new file mode 100644
index 0000000..346a618
--- /dev/null
+++ b/bitbake/lib/bb/daemonize.py
@@ -0,0 +1,193 @@
+"""
+Python Daemonizing helper
+
+Configurable daemon behaviors:
+
+    1.) The current working directory set to the "/" directory.
+    2.) The current file creation mode mask set to 0.
+    3.) Close all open files (1024). 
+    4.) Redirect standard I/O streams to "/dev/null".
+
+A failed call to fork() now raises an exception.
+
+References:
+    1) Advanced Programming in the Unix Environment: W. Richard Stevens
+	http://www.apuebook.com/apue3e.html
+    2) The Linux Programming Interface: Michael Kerrisk
+	http://man7.org/tlpi/index.html
+    3) Unix Programming Frequently Asked Questions:
+	http://www.faqs.org/faqs/unix-faq/programmer/faq/
+
+Modified to allow a function to be daemonized and return for 
+bitbake use by Richard Purdie
+"""
+
+__author__ = "Chad J. Schroeder"
+__copyright__ = "Copyright (C) 2005 Chad J. Schroeder"
+__version__ = "0.2"
+
+# Standard Python modules.
+import os                    # Miscellaneous OS interfaces.
+import sys                   # System-specific parameters and functions.
+
+# Default daemon parameters.
+# File mode creation mask of the daemon.
+# For BitBake's children, we do want to inherit the parent umask.
+UMASK = None
+
+# Default maximum for the number of available file descriptors.
+MAXFD = 1024
+
+# The standard I/O file descriptors are redirected to /dev/null by default.
+if (hasattr(os, "devnull")):
+    REDIRECT_TO = os.devnull
+else:
+    REDIRECT_TO = "/dev/null"
+
+def createDaemon(function, logfile):
+    """
+    Detach a process from the controlling terminal and run it in the
+    background as a daemon, returning control to the caller.
+    """
+
+    try:
+        # Fork a child process so the parent can exit.  This returns control to
+        # the command-line or shell.  It also guarantees that the child will not
+        # be a process group leader, since the child receives a new process ID
+        # and inherits the parent's process group ID.  This step is required
+        # to insure that the next call to os.setsid is successful.
+        pid = os.fork()
+    except OSError as e:
+        raise Exception("%s [%d]" % (e.strerror, e.errno))
+
+    if (pid == 0):      # The first child.
+        # To become the session leader of this new session and the process group
+        # leader of the new process group, we call os.setsid().  The process is
+        # also guaranteed not to have a controlling terminal.
+        os.setsid()
+
+        # Is ignoring SIGHUP necessary?
+        #
+        # It's often suggested that the SIGHUP signal should be ignored before
+        # the second fork to avoid premature termination of the process.  The
+        # reason is that when the first child terminates, all processes, e.g.
+        # the second child, in the orphaned group will be sent a SIGHUP.
+        #
+        # "However, as part of the session management system, there are exactly
+        # two cases where SIGHUP is sent on the death of a process:
+        #
+        #    1) When the process that dies is the session leader of a session that
+        #        is attached to a terminal device, SIGHUP is sent to all processes
+        #        in the foreground process group of that terminal device.
+        #    2) When the death of a process causes a process group to become
+        #        orphaned, and one or more processes in the orphaned group are
+        #        stopped, then SIGHUP and SIGCONT are sent to all members of the
+        #        orphaned group." [2]
+        #
+        # The first case can be ignored since the child is guaranteed not to have
+        # a controlling terminal.  The second case isn't so easy to dismiss.
+        # The process group is orphaned when the first child terminates and
+        # POSIX.1 requires that every STOPPED process in an orphaned process
+        # group be sent a SIGHUP signal followed by a SIGCONT signal.  Since the
+        # second child is not STOPPED though, we can safely forego ignoring the
+        # SIGHUP signal.  In any case, there are no ill-effects if it is ignored.
+        #
+        # import signal              # Set handlers for asynchronous events.
+        # signal.signal(signal.SIGHUP, signal.SIG_IGN)
+
+        try:
+            # Fork a second child and exit immediately to prevent zombies.  This
+            # causes the second child process to be orphaned, making the init
+            # process responsible for its cleanup.  And, since the first child is
+            # a session leader without a controlling terminal, it's possible for
+            # it to acquire one by opening a terminal in the future (System V-
+            # based systems).  This second fork guarantees that the child is no
+            # longer a session leader, preventing the daemon from ever acquiring
+            # a controlling terminal.
+            pid = os.fork()     # Fork a second child.
+        except OSError as e:
+            raise Exception("%s [%d]" % (e.strerror, e.errno))
+
+        if (pid == 0):  # The second child.
+            # We probably don't want the file mode creation mask inherited from
+            # the parent, so we give the child complete control over permissions.
+            if UMASK is not None:
+                os.umask(UMASK)
+        else:
+            # Parent (the first child) of the second child.
+            os._exit(0)
+    else:
+        # exit() or _exit()?
+        # _exit is like exit(), but it doesn't call any functions registered
+        # with atexit (and on_exit) or any registered signal handlers.  It also
+        # closes any open file descriptors.  Using exit() may cause all stdio
+        # streams to be flushed twice and any temporary files may be unexpectedly
+        # removed.  It's therefore recommended that child branches of a fork()
+        # and the parent branch(es) of a daemon use _exit().
+        return
+
+    # Close all open file descriptors.  This prevents the child from keeping
+    # open any file descriptors inherited from the parent.  There is a variety
+    # of methods to accomplish this task.  Three are listed below.
+    #
+    # Try the system configuration variable, SC_OPEN_MAX, to obtain the maximum
+    # number of open file descriptors to close.  If it doesn't exist, use
+    # the default value (configurable).
+    #
+    # try:
+    #     maxfd = os.sysconf("SC_OPEN_MAX")
+    # except (AttributeError, ValueError):
+    #     maxfd = MAXFD
+    #
+    # OR
+    #
+    # if (os.sysconf_names.has_key("SC_OPEN_MAX")):
+    #     maxfd = os.sysconf("SC_OPEN_MAX")
+    # else:
+    #     maxfd = MAXFD
+    #
+    # OR
+    #
+    # Use the getrlimit method to retrieve the maximum file descriptor number
+    # that can be opened by this process.  If there is no limit on the
+    # resource, use the default value.
+    #
+    import resource             # Resource usage information.
+    maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
+    if (maxfd == resource.RLIM_INFINITY):
+        maxfd = MAXFD
+  
+    # Iterate through and close all file descriptors.
+#    for fd in range(0, maxfd):
+#        try:
+#            os.close(fd)
+#        except OSError:        # ERROR, fd wasn't open to begin with (ignored)
+#            pass
+
+    # Redirect the standard I/O file descriptors to the specified file.  Since
+    # the daemon has no controlling terminal, most daemons redirect stdin,
+    # stdout, and stderr to /dev/null.  This is done to prevent side-effects
+    # from reads and writes to the standard I/O file descriptors.
+
+    # This call to open is guaranteed to return the lowest file descriptor,
+    # which will be 0 (stdin), since it was closed above.
+#    os.open(REDIRECT_TO, os.O_RDWR)    # standard input (0)
+
+    # Duplicate standard input to standard output and standard error.
+#    os.dup2(0, 1)                      # standard output (1)
+#    os.dup2(0, 2)                      # standard error (2)
+
+
+    si = file('/dev/null', 'r')
+    so = file(logfile, 'w')
+    se = so
+
+
+    # Replace those fds with our own
+    os.dup2(si.fileno(), sys.stdin.fileno())
+    os.dup2(so.fileno(), sys.stdout.fileno())
+    os.dup2(se.fileno(), sys.stderr.fileno())
+
+    function()
+
+    os._exit(0)
diff --git a/bitbake/lib/bb/data.py b/bitbake/lib/bb/data.py
new file mode 100644
index 0000000..f6415a4
--- /dev/null
+++ b/bitbake/lib/bb/data.py
@@ -0,0 +1,446 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake 'Data' implementations
+
+Functions for interacting with the data structure used by the
+BitBake build tools.
+
+The expandKeys and update_data are the most expensive
+operations. At night the cookie monster came by and
+suggested 'give me cookies on setting the variables and
+things will work out'. Taking this suggestion into account
+applying the skills from the not yet passed 'Entwurf und
+Analyse von Algorithmen' lecture and the cookie
+monster seems to be right. We will track setVar more carefully
+to have faster update_data and expandKeys operations.
+
+This is a trade-off between speed and memory again but
+the speed is more critical here.
+"""
+
+# Copyright (C) 2003, 2004  Chris Larson
+# Copyright (C) 2005        Holger Hans Peter Freyther
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Based on functions from the base bb module, Copyright 2003 Holger Schurig
+
+import sys, os, re
+if sys.argv[0][-5:] == "pydoc":
+    path = os.path.dirname(os.path.dirname(sys.argv[1]))
+else:
+    path = os.path.dirname(os.path.dirname(sys.argv[0]))
+sys.path.insert(0, path)
+from itertools import groupby
+
+from bb import data_smart
+from bb import codeparser
+import bb
+
+logger = data_smart.logger
+_dict_type = data_smart.DataSmart
+
+def init():
+    """Return a new object representing the Bitbake data"""
+    return _dict_type()
+
+def init_db(parent = None):
+    """Return a new object representing the Bitbake data,
+    optionally based on an existing object"""
+    if parent is not None:
+        return parent.createCopy()
+    else:
+        return _dict_type()
+
+def createCopy(source):
+    """Link the source set to the destination
+    If one does not find the value in the destination set,
+    search will go on to the source set to get the value.
+    Value from source are copy-on-write. i.e. any try to
+    modify one of them will end up putting the modified value
+    in the destination set.
+    """
+    return source.createCopy()
+
+def initVar(var, d):
+    """Non-destructive var init for data structure"""
+    d.initVar(var)
+
+
+def setVar(var, value, d):
+    """Set a variable to a given value"""
+    d.setVar(var, value)
+
+
+def getVar(var, d, exp = False):
+    """Gets the value of a variable"""
+    return d.getVar(var, exp)
+
+
+def renameVar(key, newkey, d):
+    """Renames a variable from key to newkey"""
+    d.renameVar(key, newkey)
+
+def delVar(var, d):
+    """Removes a variable from the data set"""
+    d.delVar(var)
+
+def appendVar(var, value, d):
+    """Append additional value to a variable"""
+    d.appendVar(var, value)
+
+def setVarFlag(var, flag, flagvalue, d):
+    """Set a flag for a given variable to a given value"""
+    d.setVarFlag(var, flag, flagvalue)
+
+def getVarFlag(var, flag, d):
+    """Gets given flag from given var"""
+    return d.getVarFlag(var, flag)
+
+def delVarFlag(var, flag, d):
+    """Removes a given flag from the variable's flags"""
+    d.delVarFlag(var, flag)
+
+def setVarFlags(var, flags, d):
+    """Set the flags for a given variable
+
+    Note:
+        setVarFlags will not clear previous
+        flags. Think of this method as
+        addVarFlags
+    """
+    d.setVarFlags(var, flags)
+
+def getVarFlags(var, d):
+    """Gets a variable's flags"""
+    return d.getVarFlags(var)
+
+def delVarFlags(var, d):
+    """Removes a variable's flags"""
+    d.delVarFlags(var)
+
+def keys(d):
+    """Return a list of keys in d"""
+    return d.keys()
+
+
+__expand_var_regexp__ = re.compile(r"\${[^{}]+}")
+__expand_python_regexp__ = re.compile(r"\${@.+?}")
+
+def expand(s, d, varname = None):
+    """Variable expansion using the data store"""
+    return d.expand(s, varname)
+
+def expandKeys(alterdata, readdata = None):
+    if readdata == None:
+        readdata = alterdata
+
+    todolist = {}
+    for key in alterdata:
+        if not '${' in key:
+            continue
+
+        ekey = expand(key, readdata)
+        if key == ekey:
+            continue
+        todolist[key] = ekey
+
+    # These two for loops are split for performance to maximise the
+    # usefulness of the expand cache
+    for key in sorted(todolist):
+        ekey = todolist[key]
+        newval = alterdata.getVar(ekey, False)
+        if newval is not None:
+            val = alterdata.getVar(key, False)
+            if val is not None:
+                bb.warn("Variable key %s (%s) replaces original key %s (%s)." % (key, val, ekey, newval))
+        alterdata.renameVar(key, ekey)
+
+def inheritFromOS(d, savedenv, permitted):
+    """Inherit variables from the initial environment."""
+    exportlist = bb.utils.preserved_envvars_exported()
+    for s in savedenv.keys():
+        if s in permitted:
+            try:
+                d.setVar(s, savedenv.getVar(s, True), op = 'from env')
+                if s in exportlist:
+                    d.setVarFlag(s, "export", True, op = 'auto env export')
+            except TypeError:
+                pass
+
+def emit_var(var, o=sys.__stdout__, d = init(), all=False):
+    """Emit a variable to be sourced by a shell."""
+    if d.getVarFlag(var, "python"):
+        return False
+
+    export = d.getVarFlag(var, "export")
+    unexport = d.getVarFlag(var, "unexport")
+    func = d.getVarFlag(var, "func")
+    if not all and not export and not unexport and not func:
+        return False
+
+    try:
+        if all:
+            oval = d.getVar(var, False)
+        val = d.getVar(var, True)
+    except (KeyboardInterrupt, bb.build.FuncFailed):
+        raise
+    except Exception as exc:
+        o.write('# expansion of %s threw %s: %s\n' % (var, exc.__class__.__name__, str(exc)))
+        return False
+
+    if all:
+        d.varhistory.emit(var, oval, val, o, d)
+
+    if (var.find("-") != -1 or var.find(".") != -1 or var.find('{') != -1 or var.find('}') != -1 or var.find('+') != -1) and not all:
+        return False
+
+    varExpanded = d.expand(var)
+
+    if unexport:
+        o.write('unset %s\n' % varExpanded)
+        return False
+
+    if val is None:
+        return False
+
+    val = str(val)
+
+    if varExpanded.startswith("BASH_FUNC_"):
+        varExpanded = varExpanded[10:-2]
+        val = val[3:] # Strip off "() "
+        o.write("%s() %s\n" % (varExpanded, val))
+        o.write("export -f %s\n" % (varExpanded))
+        return True
+
+    if func:
+        # NOTE: should probably check for unbalanced {} within the var
+        o.write("%s() {\n%s\n}\n" % (varExpanded, val))
+        return 1
+
+    if export:
+        o.write('export ')
+
+    # if we're going to output this within doublequotes,
+    # to a shell, we need to escape the quotes in the var
+    alter = re.sub('"', '\\"', val)
+    alter = re.sub('\n', ' \\\n', alter)
+    alter = re.sub('\\$', '\\\\$', alter)
+    o.write('%s="%s"\n' % (varExpanded, alter))
+    return False
+
+def emit_env(o=sys.__stdout__, d = init(), all=False):
+    """Emits all items in the data store in a format such that it can be sourced by a shell."""
+
+    isfunc = lambda key: bool(d.getVarFlag(key, "func"))
+    keys = sorted((key for key in d.keys() if not key.startswith("__")), key=isfunc)
+    grouped = groupby(keys, isfunc)
+    for isfunc, keys in grouped:
+        for key in keys:
+            emit_var(key, o, d, all and not isfunc) and o.write('\n')
+
+def exported_keys(d):
+    return (key for key in d.keys() if not key.startswith('__') and
+                                      d.getVarFlag(key, 'export') and
+                                      not d.getVarFlag(key, 'unexport'))
+
+def exported_vars(d):
+    for key in exported_keys(d):
+        try:
+            value = d.getVar(key, True)
+        except Exception:
+            pass
+
+        if value is not None:
+            yield key, str(value)
+
+def emit_func(func, o=sys.__stdout__, d = init()):
+    """Emits all items in the data store in a format such that it can be sourced by a shell."""
+
+    keys = (key for key in d.keys() if not key.startswith("__") and not d.getVarFlag(key, "func"))
+    for key in keys:
+        emit_var(key, o, d, False)
+
+    o.write('\n')
+    emit_var(func, o, d, False) and o.write('\n')
+    newdeps = bb.codeparser.ShellParser(func, logger).parse_shell(d.getVar(func, True))
+    newdeps |= set((d.getVarFlag(func, "vardeps", True) or "").split())
+    seen = set()
+    while newdeps:
+        deps = newdeps
+        seen |= deps
+        newdeps = set()
+        for dep in deps:
+            if d.getVarFlag(dep, "func") and not d.getVarFlag(dep, "python"):
+               emit_var(dep, o, d, False) and o.write('\n')
+               newdeps |=  bb.codeparser.ShellParser(dep, logger).parse_shell(d.getVar(dep, True))
+               newdeps |= set((d.getVarFlag(dep, "vardeps", True) or "").split())
+        newdeps -= seen
+
+_functionfmt = """
+def {function}(d):
+{body}"""
+
+def emit_func_python(func, o=sys.__stdout__, d = init()):
+    """Emits all items in the data store in a format such that it can be sourced by a shell."""
+
+    def write_func(func, o, call = False):
+        body = d.getVar(func, True)
+        if not body.startswith("def"):
+            body = _functionfmt.format(function=func, body=body)
+
+        o.write(body.strip() + "\n\n")
+        if call:
+            o.write(func + "(d)" + "\n\n")
+
+    write_func(func, o, True)
+    pp = bb.codeparser.PythonParser(func, logger)
+    pp.parse_python(d.getVar(func, True))
+    newdeps = pp.execs
+    newdeps |= set((d.getVarFlag(func, "vardeps", True) or "").split())
+    seen = set()
+    while newdeps:
+        deps = newdeps
+        seen |= deps
+        newdeps = set()
+        for dep in deps:
+            if d.getVarFlag(dep, "func") and d.getVarFlag(dep, "python"):
+               write_func(dep, o)
+               pp = bb.codeparser.PythonParser(dep, logger)
+               pp.parse_python(d.getVar(dep, True))
+               newdeps |= pp.execs
+               newdeps |= set((d.getVarFlag(dep, "vardeps", True) or "").split())
+        newdeps -= seen
+
+def update_data(d):
+    """Performs final steps upon the datastore, including application of overrides"""
+    d.finalize(parent = True)
+
+def build_dependencies(key, keys, shelldeps, varflagsexcl, d):
+    deps = set()
+    try:
+        if key[-1] == ']':
+            vf = key[:-1].split('[')
+            value = d.getVarFlag(vf[0], vf[1], False)
+            parser = d.expandWithRefs(value, key)
+            deps |= parser.references
+            deps = deps | (keys & parser.execs)
+            return deps, value
+        varflags = d.getVarFlags(key, ["vardeps", "vardepvalue", "vardepsexclude", "vardepvalueexclude", "postfuncs", "prefuncs"]) or {}
+        vardeps = varflags.get("vardeps")
+        value = d.getVar(key, False)
+
+        def handle_contains(value, contains, d):
+            newvalue = ""
+            for k in sorted(contains):
+                l = (d.getVar(k, True) or "").split()
+                for word in sorted(contains[k]):
+                    if word in l:
+                        newvalue += "\n%s{%s} = Set" %  (k, word)
+                    else:
+                        newvalue += "\n%s{%s} = Unset" %  (k, word)
+            if not newvalue:
+                return value
+            if not value:
+                return newvalue
+            return value + newvalue
+
+        if "vardepvalue" in varflags:
+           value = varflags.get("vardepvalue")
+        elif varflags.get("func"):
+            if varflags.get("python"):
+                parsedvar = d.expandWithRefs(value, key)
+                parser = bb.codeparser.PythonParser(key, logger)
+                if parsedvar.value and "\t" in parsedvar.value:
+                    logger.warn("Variable %s contains tabs, please remove these (%s)" % (key, d.getVar("FILE", True)))
+                parser.parse_python(parsedvar.value)
+                deps = deps | parser.references
+                value = handle_contains(value, parser.contains, d)
+            else:
+                parsedvar = d.expandWithRefs(value, key)
+                parser = bb.codeparser.ShellParser(key, logger)
+                parser.parse_shell(parsedvar.value)
+                deps = deps | shelldeps
+            if vardeps is None:
+                parser.log.flush()
+            if "prefuncs" in varflags:
+                deps = deps | set(varflags["prefuncs"].split())
+            if "postfuncs" in varflags:
+                deps = deps | set(varflags["postfuncs"].split())
+            deps = deps | parsedvar.references
+            deps = deps | (keys & parser.execs) | (keys & parsedvar.execs)
+            value = handle_contains(value, parsedvar.contains, d)
+        else:
+            parser = d.expandWithRefs(value, key)
+            deps |= parser.references
+            deps = deps | (keys & parser.execs)
+            value = handle_contains(value, parser.contains, d)
+
+        if "vardepvalueexclude" in varflags:
+            exclude = varflags.get("vardepvalueexclude")
+            for excl in exclude.split('|'):
+                if excl:
+                    value = value.replace(excl, '')
+
+        # Add varflags, assuming an exclusion list is set
+        if varflagsexcl:
+            varfdeps = []
+            for f in varflags:
+                if f not in varflagsexcl:
+                    varfdeps.append('%s[%s]' % (key, f))
+            if varfdeps:
+                deps |= set(varfdeps)
+
+        deps |= set((vardeps or "").split())
+        deps -= set(varflags.get("vardepsexclude", "").split())
+    except Exception as e:
+        raise bb.data_smart.ExpansionError(key, None, e)
+    return deps, value
+    #bb.note("Variable %s references %s and calls %s" % (key, str(deps), str(execs)))
+    #d.setVarFlag(key, "vardeps", deps)
+
+def generate_dependencies(d):
+
+    keys = set(key for key in d if not key.startswith("__"))
+    shelldeps = set(key for key in d.getVar("__exportlist", False) if d.getVarFlag(key, "export") and not d.getVarFlag(key, "unexport"))
+    varflagsexcl = d.getVar('BB_SIGNATURE_EXCLUDE_FLAGS', True)
+
+    deps = {}
+    values = {}
+
+    tasklist = d.getVar('__BBTASKS', False) or []
+    for task in tasklist:
+        deps[task], values[task] = build_dependencies(task, keys, shelldeps, varflagsexcl, d)
+        newdeps = deps[task]
+        seen = set()
+        while newdeps:
+            nextdeps = newdeps
+            seen |= nextdeps
+            newdeps = set()
+            for dep in nextdeps:
+                if dep not in deps:
+                    deps[dep], values[dep] = build_dependencies(dep, keys, shelldeps, varflagsexcl, d)
+                newdeps |=  deps[dep]
+            newdeps -= seen
+        #print "For %s: %s" % (task, str(deps[task]))
+    return tasklist, deps, values
+
+def inherits_class(klass, d):
+    val = d.getVar('__inherit_cache', False) or []
+    needle = os.path.join('classes', '%s.bbclass' % klass)
+    for v in val:
+        if v.endswith(needle):
+            return True
+    return False
diff --git a/bitbake/lib/bb/data_smart.py b/bitbake/lib/bb/data_smart.py
new file mode 100644
index 0000000..79b4ed9
--- /dev/null
+++ b/bitbake/lib/bb/data_smart.py
@@ -0,0 +1,942 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake Smart Dictionary Implementation
+
+Functions for interacting with the data structure used by the
+BitBake build tools.
+
+"""
+
+# Copyright (C) 2003, 2004  Chris Larson
+# Copyright (C) 2004, 2005  Seb Frankengul
+# Copyright (C) 2005, 2006  Holger Hans Peter Freyther
+# Copyright (C) 2005        Uli Luckas
+# Copyright (C) 2005        ROAD GmbH
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+# Based on functions from the base bb module, Copyright 2003 Holger Schurig
+
+import copy, re, sys, traceback
+from collections import MutableMapping
+import logging
+import hashlib
+import bb, bb.codeparser
+from bb   import utils
+from bb.COW  import COWDictBase
+
+logger = logging.getLogger("BitBake.Data")
+
+__setvar_keyword__ = ["_append", "_prepend", "_remove"]
+__setvar_regexp__ = re.compile('(?P<base>.*?)(?P<keyword>_append|_prepend|_remove)(_(?P<add>.*))?$')
+__expand_var_regexp__ = re.compile(r"\${[^{}@\n\t ]+}")
+__expand_python_regexp__ = re.compile(r"\${@.+?}")
+
+def infer_caller_details(loginfo, parent = False, varval = True):
+    """Save the caller the trouble of specifying everything."""
+    # Save effort.
+    if 'ignore' in loginfo and loginfo['ignore']:
+        return
+    # If nothing was provided, mark this as possibly unneeded.
+    if not loginfo:
+        loginfo['ignore'] = True
+        return
+    # Infer caller's likely values for variable (var) and value (value), 
+    # to reduce clutter in the rest of the code.
+    above = None
+    def set_above():
+        try:
+            raise Exception
+        except Exception:
+            tb = sys.exc_info()[2]
+            if parent:
+                return tb.tb_frame.f_back.f_back.f_back
+            else:
+                return tb.tb_frame.f_back.f_back
+
+    if varval and ('variable' not in loginfo or 'detail' not in loginfo):
+        if not above:
+            above = set_above()
+        lcls = above.f_locals.items()
+        for k, v in lcls:
+            if k == 'value' and 'detail' not in loginfo:
+                loginfo['detail'] = v
+            if k == 'var' and 'variable' not in loginfo:
+                loginfo['variable'] = v
+    # Infer file/line/function from traceback
+    # Don't use traceback.extract_stack() since it fills the line contents which
+    # we don't need and that hits stat syscalls
+    if 'file' not in loginfo:
+        if not above:
+            above = set_above()
+        f = above.f_back
+        line = f.f_lineno
+        file = f.f_code.co_filename
+        func = f.f_code.co_name
+        loginfo['file'] = file
+        loginfo['line'] = line
+        if func not in loginfo:
+            loginfo['func'] = func
+
+class VariableParse:
+    def __init__(self, varname, d, val = None):
+        self.varname = varname
+        self.d = d
+        self.value = val
+
+        self.references = set()
+        self.execs = set()
+        self.contains = {}
+
+    def var_sub(self, match):
+            key = match.group()[2:-1]
+            if self.varname and key:
+                if self.varname == key:
+                    raise Exception("variable %s references itself!" % self.varname)
+            if key in self.d.expand_cache:
+                varparse = self.d.expand_cache[key]
+                var = varparse.value
+            else:
+                var = self.d.getVarFlag(key, "_content", True)
+            self.references.add(key)
+            if var is not None:
+                return var
+            else:
+                return match.group()
+
+    def python_sub(self, match):
+            code = match.group()[3:-1]
+            codeobj = compile(code.strip(), self.varname or "<expansion>", "eval")
+
+            parser = bb.codeparser.PythonParser(self.varname, logger)
+            parser.parse_python(code)
+            if self.varname:
+                vardeps = self.d.getVarFlag(self.varname, "vardeps", True)
+                if vardeps is None:
+                    parser.log.flush()
+            else:
+                parser.log.flush()
+            self.references |= parser.references
+            self.execs |= parser.execs
+
+            for k in parser.contains:
+                if k not in self.contains:
+                    self.contains[k] = parser.contains[k].copy()
+                else:
+                    self.contains[k].update(parser.contains[k])
+            value = utils.better_eval(codeobj, DataContext(self.d))
+            return str(value)
+
+
+class DataContext(dict):
+    def __init__(self, metadata, **kwargs):
+        self.metadata = metadata
+        dict.__init__(self, **kwargs)
+        self['d'] = metadata
+
+    def __missing__(self, key):
+        value = self.metadata.getVar(key, True)
+        if value is None or self.metadata.getVarFlag(key, 'func'):
+            raise KeyError(key)
+        else:
+            return value
+
+class ExpansionError(Exception):
+    def __init__(self, varname, expression, exception):
+        self.expression = expression
+        self.variablename = varname
+        self.exception = exception
+        if varname:
+            if expression:
+                self.msg = "Failure expanding variable %s, expression was %s which triggered exception %s: %s" % (varname, expression, type(exception).__name__, exception)
+            else:
+                self.msg = "Failure expanding variable %s: %s: %s" % (varname, type(exception).__name__, exception)
+        else:
+            self.msg = "Failure expanding expression %s which triggered exception %s: %s" % (expression, type(exception).__name__, exception)
+        Exception.__init__(self, self.msg)
+        self.args = (varname, expression, exception)
+    def __str__(self):
+        return self.msg
+
+class IncludeHistory(object):
+    def __init__(self, parent = None, filename = '[TOP LEVEL]'):
+        self.parent = parent
+        self.filename = filename
+        self.children = []
+        self.current = self
+
+    def copy(self):
+        new = IncludeHistory(self.parent, self.filename)
+        for c in self.children:
+            new.children.append(c)
+        return new
+
+    def include(self, filename):
+        newfile = IncludeHistory(self.current, filename)
+        self.current.children.append(newfile)
+        self.current = newfile
+        return self
+
+    def __enter__(self):
+        pass
+
+    def __exit__(self, a, b, c):
+        if self.current.parent:
+            self.current = self.current.parent
+        else:
+            bb.warn("Include log: Tried to finish '%s' at top level." % filename)
+        return False
+
+    def emit(self, o, level = 0):
+        """Emit an include history file, and its children."""
+        if level:
+            spaces = "  " * (level - 1)
+            o.write("# %s%s" % (spaces, self.filename))
+            if len(self.children) > 0:
+                o.write(" includes:")
+        else:
+            o.write("#\n# INCLUDE HISTORY:\n#")
+        level = level + 1
+        for child in self.children:
+            o.write("\n")
+            child.emit(o, level)
+
+class VariableHistory(object):
+    def __init__(self, dataroot):
+        self.dataroot = dataroot
+        self.variables = COWDictBase.copy()
+
+    def copy(self):
+        new = VariableHistory(self.dataroot)
+        new.variables = self.variables.copy()
+        return new
+
+    def record(self, *kwonly, **loginfo):
+        if not self.dataroot._tracking:
+            return
+        if len(kwonly) > 0:
+            raise TypeError
+        infer_caller_details(loginfo, parent = True)
+        if 'ignore' in loginfo and loginfo['ignore']:
+            return
+        if 'op' not in loginfo or not loginfo['op']:
+            loginfo['op'] = 'set'
+        if 'detail' in loginfo:
+            loginfo['detail'] = str(loginfo['detail'])
+        if 'variable' not in loginfo or 'file' not in loginfo:
+            raise ValueError("record() missing variable or file.")
+        var = loginfo['variable']
+
+        if var not in self.variables:
+            self.variables[var] = []
+        if not isinstance(self.variables[var], list):
+            return
+        if 'nodups' in loginfo and loginfo in self.variables[var]:
+            return
+        self.variables[var].append(loginfo.copy())
+
+    def variable(self, var):
+        if var in self.variables:
+            return self.variables[var]
+        else:
+            return []
+
+    def emit(self, var, oval, val, o, d):
+        history = self.variable(var)
+
+        # Append override history
+        if var in d.overridedata:
+            for (r, override) in d.overridedata[var]:
+                for event in self.variable(r):
+                    loginfo = event.copy()
+                    if 'flag' in loginfo and not loginfo['flag'].startswith("_"):
+                        continue
+                    loginfo['variable'] = var
+                    loginfo['op'] = 'override[%s]:%s' % (override, loginfo['op'])
+                    history.append(loginfo)
+
+        commentVal = re.sub('\n', '\n#', str(oval))
+        if history:
+            if len(history) == 1:
+                o.write("#\n# $%s\n" % var)
+            else:
+                o.write("#\n# $%s [%d operations]\n" % (var, len(history)))
+            for event in history:
+                # o.write("# %s\n" % str(event))
+                if 'func' in event:
+                    # If we have a function listed, this is internal
+                    # code, not an operation in a config file, and the
+                    # full path is distracting.
+                    event['file'] = re.sub('.*/', '', event['file'])
+                    display_func = ' [%s]' % event['func']
+                else:
+                    display_func = ''
+                if 'flag' in event:
+                    flag = '[%s] ' % (event['flag'])
+                else:
+                    flag = ''
+                o.write("#   %s %s:%s%s\n#     %s\"%s\"\n" % (event['op'], event['file'], event['line'], display_func, flag, re.sub('\n', '\n#     ', event['detail'])))
+            if len(history) > 1:
+                o.write("# pre-expansion value:\n")
+                o.write('#   "%s"\n' % (commentVal))
+        else:
+            o.write("#\n# $%s\n#   [no history recorded]\n#\n" % var)
+            o.write('#   "%s"\n' % (commentVal))
+
+    def get_variable_files(self, var):
+        """Get the files where operations are made on a variable"""
+        var_history = self.variable(var)
+        files = []
+        for event in var_history:
+            files.append(event['file'])
+        return files
+
+    def get_variable_lines(self, var, f):
+        """Get the line where a operation is made on a variable in file f"""
+        var_history = self.variable(var)
+        lines = []
+        for event in var_history:
+            if f== event['file']:
+                line = event['line']
+                lines.append(line)
+        return lines
+
+    def get_variable_items_files(self, var, d):
+        """
+        Use variable history to map items added to a list variable and
+        the files in which they were added.
+        """
+        history = self.variable(var)
+        finalitems = (d.getVar(var, True) or '').split()
+        filemap = {}
+        isset = False
+        for event in history:
+            if 'flag' in event:
+                continue
+            if event['op'] == '_remove':
+                continue
+            if isset and event['op'] == 'set?':
+                continue
+            isset = True
+            items = d.expand(event['detail']).split()
+            for item in items:
+                # This is a little crude but is belt-and-braces to avoid us
+                # having to handle every possible operation type specifically
+                if item in finalitems and not item in filemap:
+                    filemap[item] = event['file']
+        return filemap
+
+    def del_var_history(self, var, f=None, line=None):
+        """If file f and line are not given, the entire history of var is deleted"""
+        if var in self.variables:
+            if f and line:
+                self.variables[var] = [ x for x in self.variables[var] if x['file']!=f and x['line']!=line]
+            else:
+                self.variables[var] = []
+
+class DataSmart(MutableMapping):
+    def __init__(self):
+        self.dict = {}
+
+        self.inchistory = IncludeHistory()
+        self.varhistory = VariableHistory(self)
+        self._tracking = False
+
+        self.expand_cache = {}
+
+        # cookie monster tribute
+        # Need to be careful about writes to overridedata as
+        # its only a shallow copy, could influence other data store
+        # copies!
+        self.overridedata = {}
+        self.overrides = None
+        self.overridevars = set(["OVERRIDES", "FILE"])
+        self.inoverride = False
+
+    def enableTracking(self):
+        self._tracking = True
+
+    def disableTracking(self):
+        self._tracking = False
+
+    def expandWithRefs(self, s, varname):
+
+        if not isinstance(s, basestring): # sanity check
+            return VariableParse(varname, self, s)
+
+        if varname and varname in self.expand_cache:
+            return self.expand_cache[varname]
+
+        varparse = VariableParse(varname, self)
+
+        while s.find('${') != -1:
+            olds = s
+            try:
+                s = __expand_var_regexp__.sub(varparse.var_sub, s)
+                s = __expand_python_regexp__.sub(varparse.python_sub, s)
+                if s == olds:
+                    break
+            except ExpansionError:
+                raise
+            except bb.parse.SkipRecipe:
+                raise
+            except Exception as exc:
+                exc_class, exc, tb = sys.exc_info()
+                raise ExpansionError, ExpansionError(varname, s, exc), tb
+
+        varparse.value = s
+
+        if varname:
+            self.expand_cache[varname] = varparse
+
+        return varparse
+
+    def expand(self, s, varname = None):
+        return self.expandWithRefs(s, varname).value
+
+    def finalize(self, parent = False):
+        return
+
+    def internal_finalize(self, parent = False):
+        """Performs final steps upon the datastore, including application of overrides"""
+        self.overrides = None
+
+    def need_overrides(self):
+        if self.overrides is None:
+            if self.inoverride:
+                return
+            self.inoverride = True
+            # Can end up here recursively so setup dummy values
+            self.overrides = []
+            self.overridesset = set()
+            self.overrides = (self.getVar("OVERRIDES", True) or "").split(":") or []
+            self.overridesset = set(self.overrides)
+            self.inoverride = False
+            self.expand_cache = {}
+
+    def initVar(self, var):
+        self.expand_cache = {}
+        if not var in self.dict:
+            self.dict[var] = {}
+
+    def _findVar(self, var):
+        dest = self.dict
+        while dest:
+            if var in dest:
+                return dest[var]
+
+            if "_data" not in dest:
+                break
+            dest = dest["_data"]
+
+    def _makeShadowCopy(self, var):
+        if var in self.dict:
+            return
+
+        local_var = self._findVar(var)
+
+        if local_var:
+            self.dict[var] = copy.copy(local_var)
+        else:
+            self.initVar(var)
+
+
+    def setVar(self, var, value, **loginfo):
+        #print("var=" + str(var) + "  val=" + str(value))
+        parsing=False
+        if 'parsing' in loginfo:
+            parsing=True
+
+        if 'op' not in loginfo:
+            loginfo['op'] = "set"
+        self.expand_cache = {}
+        match  = __setvar_regexp__.match(var)
+        if match and match.group("keyword") in __setvar_keyword__:
+            base = match.group('base')
+            keyword = match.group("keyword")
+            override = match.group('add')
+            l = self.getVarFlag(base, keyword) or []
+            l.append([value, override])
+            self.setVarFlag(base, keyword, l, ignore=True)
+            # And cause that to be recorded:
+            loginfo['detail'] = value
+            loginfo['variable'] = base
+            if override:
+                loginfo['op'] = '%s[%s]' % (keyword, override)
+            else:
+                loginfo['op'] = keyword
+            self.varhistory.record(**loginfo)
+            # todo make sure keyword is not __doc__ or __module__
+            # pay the cookie monster
+
+            # more cookies for the cookie monster
+            if '_' in var:
+                self._setvar_update_overrides(base, **loginfo)
+
+
+            if base in self.overridevars:
+                self.overridevars.update(self.expandWithRefs(value, var).references)
+                self.internal_finalize(True)
+            return
+
+        if not var in self.dict:
+            self._makeShadowCopy(var)
+
+        if not parsing:
+            if "_append" in self.dict[var]:
+                del self.dict[var]["_append"]
+            if "_prepend" in self.dict[var]:
+                del self.dict[var]["_prepend"]
+            if var in self.overridedata:
+                active = []
+                self.need_overrides()
+                for (r, o) in self.overridedata[var]:
+                    if o in self.overridesset:
+                        active.append(r)
+                    elif "_" in o:
+                        if set(o.split("_")).issubset(self.overridesset):
+                            active.append(r)
+                for a in active:
+                    self.delVar(a)
+                del self.overridedata[var]
+
+        # more cookies for the cookie monster
+        if '_' in var:
+            self._setvar_update_overrides(var, **loginfo)
+
+        # setting var
+        self.dict[var]["_content"] = value
+        self.varhistory.record(**loginfo)
+
+        if var in self.overridevars:
+            self.overridevars.update(self.expandWithRefs(value, var).references)
+            self.internal_finalize(True)
+
+    def _setvar_update_overrides(self, var, **loginfo):
+        # aka pay the cookie monster
+        override = var[var.rfind('_')+1:]
+        shortvar = var[:var.rfind('_')]
+        while override:
+            if shortvar not in self.overridedata:
+                self.overridedata[shortvar] = []
+            if [var, override] not in self.overridedata[shortvar]:
+                # Force CoW by recreating the list first
+                self.overridedata[shortvar] = list(self.overridedata[shortvar])
+                self.overridedata[shortvar].append([var, override])
+            override = None
+            if "_" in shortvar:
+                override = var[shortvar.rfind('_')+1:]
+                shortvar = var[:shortvar.rfind('_')]
+                if len(shortvar) == 0:
+                    override = None
+
+    def getVar(self, var, expand=False, noweakdefault=False, parsing=False):
+        return self.getVarFlag(var, "_content", expand, noweakdefault, parsing)
+
+    def renameVar(self, key, newkey, **loginfo):
+        """
+        Rename the variable key to newkey
+        """
+        val = self.getVar(key, 0, parsing=True)
+        if val is not None:
+            loginfo['variable'] = newkey
+            loginfo['op'] = 'rename from %s' % key
+            loginfo['detail'] = val
+            self.varhistory.record(**loginfo)
+            self.setVar(newkey, val, ignore=True, parsing=True)
+
+        for i in (__setvar_keyword__):
+            src = self.getVarFlag(key, i)
+            if src is None:
+                continue
+
+            dest = self.getVarFlag(newkey, i) or []
+            dest.extend(src)
+            self.setVarFlag(newkey, i, dest, ignore=True)
+
+        if key in self.overridedata:
+            self.overridedata[newkey] = []
+            for (v, o) in self.overridedata[key]:
+                self.overridedata[newkey].append([v.replace(key, newkey), o])
+                self.renameVar(v, v.replace(key, newkey))
+
+        if '_' in newkey and val is None:
+            self._setvar_update_overrides(newkey, **loginfo)
+
+        loginfo['variable'] = key
+        loginfo['op'] = 'rename (to)'
+        loginfo['detail'] = newkey
+        self.varhistory.record(**loginfo)
+        self.delVar(key, ignore=True)
+
+    def appendVar(self, var, value, **loginfo):
+        loginfo['op'] = 'append'
+        self.varhistory.record(**loginfo)
+        self.setVar(var + "_append", value, ignore=True, parsing=True)
+
+    def prependVar(self, var, value, **loginfo):
+        loginfo['op'] = 'prepend'
+        self.varhistory.record(**loginfo)
+        self.setVar(var + "_prepend", value, ignore=True, parsing=True)
+
+    def delVar(self, var, **loginfo):
+        loginfo['detail'] = ""
+        loginfo['op'] = 'del'
+        self.varhistory.record(**loginfo)
+        self.expand_cache = {}
+        self.dict[var] = {}
+        if var in self.overridedata:
+            del self.overridedata[var]
+        if '_' in var:
+            override = var[var.rfind('_')+1:]
+            shortvar = var[:var.rfind('_')]
+            while override:
+                try:
+                    if shortvar in self.overridedata:
+                        # Force CoW by recreating the list first
+                        self.overridedata[shortvar] = list(self.overridedata[shortvar])
+                        self.overridedata[shortvar].remove([var, override])
+                except ValueError as e:
+                    pass
+                override = None
+                if "_" in shortvar:
+                    override = var[shortvar.rfind('_')+1:]
+                    shortvar = var[:shortvar.rfind('_')]
+                    if len(shortvar) == 0:
+                         override = None
+
+    def setVarFlag(self, var, flag, value, **loginfo):
+        self.expand_cache = {}
+        if 'op' not in loginfo:
+            loginfo['op'] = "set"
+        loginfo['flag'] = flag
+        self.varhistory.record(**loginfo)
+        if not var in self.dict:
+            self._makeShadowCopy(var)
+        self.dict[var][flag] = value
+
+        if flag == "_defaultval" and '_' in var:
+            self._setvar_update_overrides(var, **loginfo)
+
+        if flag == "unexport" or flag == "export":
+            if not "__exportlist" in self.dict:
+                self._makeShadowCopy("__exportlist")
+            if not "_content" in self.dict["__exportlist"]:
+                self.dict["__exportlist"]["_content"] = set()
+            self.dict["__exportlist"]["_content"].add(var)
+
+    def getVarFlag(self, var, flag, expand=False, noweakdefault=False, parsing=False):
+        local_var = self._findVar(var)
+        value = None
+        if flag == "_content" and var in self.overridedata and not parsing:
+            match = False
+            active = {}
+            self.need_overrides()
+            for (r, o) in self.overridedata[var]:
+                # What about double overrides both with "_" in the name?
+                if o in self.overridesset:
+                    active[o] = r
+                elif "_" in o:
+                    if set(o.split("_")).issubset(self.overridesset):
+                        active[o] = r
+
+            mod = True
+            while mod:
+                mod = False
+                for o in self.overrides:
+                    for a in active.copy():
+                        if a.endswith("_" + o):
+                            t = active[a]
+                            del active[a]
+                            active[a.replace("_" + o, "")] = t
+                            mod = True
+                        elif a == o:
+                            match = active[a]
+                            del active[a]
+            if match:
+                value = self.getVar(match)
+
+        if local_var is not None and value is None:
+            if flag in local_var:
+                value = copy.copy(local_var[flag])
+            elif flag == "_content" and "_defaultval" in local_var and not noweakdefault:
+                value = copy.copy(local_var["_defaultval"])
+
+
+        if flag == "_content" and local_var is not None and "_append" in local_var and not parsing:
+            if not value:
+                value = ""
+            self.need_overrides()
+            for (r, o) in local_var["_append"]:
+                match = True
+                if o:
+                    for o2 in o.split("_"):
+                        if not o2 in self.overrides:
+                            match = False                            
+                if match:
+                    value = value + r
+
+        if flag == "_content" and local_var is not None and "_prepend" in local_var and not parsing:
+            if not value:
+                value = ""
+            self.need_overrides()
+            for (r, o) in local_var["_prepend"]:
+
+                match = True
+                if o:
+                    for o2 in o.split("_"):
+                        if not o2 in self.overrides:
+                            match = False                            
+                if match:
+                    value = r + value
+
+        if expand and value:
+            # Only getvar (flag == _content) hits the expand cache
+            cachename = None
+            if flag == "_content":
+                cachename = var
+            else:
+                cachename = var + "[" + flag + "]"
+            value = self.expand(value, cachename)
+
+        if value and flag == "_content" and local_var is not None and "_remove" in local_var:
+            removes = []
+            self.need_overrides()
+            for (r, o) in local_var["_remove"]:
+                match = True
+                if o:
+                    for o2 in o.split("_"):
+                        if not o2 in self.overrides:
+                            match = False                            
+                if match:
+                    removes.extend(self.expand(r).split())
+
+            filtered = filter(lambda v: v not in removes,
+                              value.split())
+            value = " ".join(filtered)
+            if expand and var in self.expand_cache:
+                 # We need to ensure the expand cache has the correct value
+                 # flag == "_content" here
+                self.expand_cache[var].value = value
+        return value
+
+    def delVarFlag(self, var, flag, **loginfo):
+        self.expand_cache = {}
+        local_var = self._findVar(var)
+        if not local_var:
+            return
+        if not var in self.dict:
+            self._makeShadowCopy(var)
+
+        if var in self.dict and flag in self.dict[var]:
+            loginfo['detail'] = ""
+            loginfo['op'] = 'delFlag'
+            loginfo['flag'] = flag
+            self.varhistory.record(**loginfo)
+
+            del self.dict[var][flag]
+
+    def appendVarFlag(self, var, flag, value, **loginfo):
+        loginfo['op'] = 'append'
+        loginfo['flag'] = flag
+        self.varhistory.record(**loginfo)
+        newvalue = (self.getVarFlag(var, flag, False) or "") + value
+        self.setVarFlag(var, flag, newvalue, ignore=True)
+
+    def prependVarFlag(self, var, flag, value, **loginfo):
+        loginfo['op'] = 'prepend'
+        loginfo['flag'] = flag
+        self.varhistory.record(**loginfo)
+        newvalue = value + (self.getVarFlag(var, flag, False) or "")
+        self.setVarFlag(var, flag, newvalue, ignore=True)
+
+    def setVarFlags(self, var, flags, **loginfo):
+        self.expand_cache = {}
+        infer_caller_details(loginfo)
+        if not var in self.dict:
+            self._makeShadowCopy(var)
+
+        for i in flags:
+            if i == "_content":
+                continue
+            loginfo['flag'] = i
+            loginfo['detail'] = flags[i]
+            self.varhistory.record(**loginfo)
+            self.dict[var][i] = flags[i]
+
+    def getVarFlags(self, var, expand = False, internalflags=False):
+        local_var = self._findVar(var)
+        flags = {}
+
+        if local_var:
+            for i in local_var:
+                if i.startswith("_") and not internalflags:
+                    continue
+                flags[i] = local_var[i]
+                if expand and i in expand:
+                    flags[i] = self.expand(flags[i], var + "[" + i + "]")
+        if len(flags) == 0:
+            return None
+        return flags
+
+
+    def delVarFlags(self, var, **loginfo):
+        self.expand_cache = {}
+        if not var in self.dict:
+            self._makeShadowCopy(var)
+
+        if var in self.dict:
+            content = None
+
+            loginfo['op'] = 'delete flags'
+            self.varhistory.record(**loginfo)
+
+            # try to save the content
+            if "_content" in self.dict[var]:
+                content  = self.dict[var]["_content"]
+                self.dict[var]            = {}
+                self.dict[var]["_content"] = content
+            else:
+                del self.dict[var]
+
+    def createCopy(self):
+        """
+        Create a copy of self by setting _data to self
+        """
+        # we really want this to be a DataSmart...
+        data = DataSmart()
+        data.dict["_data"] = self.dict
+        data.varhistory = self.varhistory.copy()
+        data.varhistory.datasmart = data
+        data.inchistory = self.inchistory.copy()
+
+        data._tracking = self._tracking
+
+        data.overrides = None
+        data.overridevars = copy.copy(self.overridevars)
+        # Should really be a deepcopy but has heavy overhead.
+        # Instead, we're careful with writes.
+        data.overridedata = copy.copy(self.overridedata)
+
+        return data
+
+    def expandVarref(self, variable, parents=False):
+        """Find all references to variable in the data and expand it
+           in place, optionally descending to parent datastores."""
+
+        if parents:
+            keys = iter(self)
+        else:
+            keys = self.localkeys()
+
+        ref = '${%s}' % variable
+        value = self.getVar(variable, False)
+        for key in keys:
+            referrervalue = self.getVar(key, False)
+            if referrervalue and ref in referrervalue:
+                self.setVar(key, referrervalue.replace(ref, value))
+
+    def localkeys(self):
+        for key in self.dict:
+            if key != '_data':
+                yield key
+
+    def __iter__(self):
+        deleted = set()
+        overrides = set()
+        def keylist(d):        
+            klist = set()
+            for key in d:
+                if key == "_data":
+                    continue
+                if key in deleted:
+                    continue
+                if key in overrides:
+                    continue
+                if not d[key]:
+                    deleted.add(key)
+                    continue
+                klist.add(key)
+
+            if "_data" in d:
+                klist |= keylist(d["_data"])
+
+            return klist
+
+        self.need_overrides()
+        for var in self.overridedata:
+            for (r, o) in self.overridedata[var]:
+                if o in self.overridesset:
+                    overrides.add(var)
+                elif "_" in o:
+                    if set(o.split("_")).issubset(self.overridesset):
+                        overrides.add(var)
+
+        for k in keylist(self.dict):
+             yield k
+
+        for k in overrides:
+             yield k
+
+    def __len__(self):
+        return len(frozenset(self))
+
+    def __getitem__(self, item):
+        value = self.getVar(item, False)
+        if value is None:
+            raise KeyError(item)
+        else:
+            return value
+
+    def __setitem__(self, var, value):
+        self.setVar(var, value)
+
+    def __delitem__(self, var):
+        self.delVar(var)
+
+    def get_hash(self):
+        data = {}
+        d = self.createCopy()
+        bb.data.expandKeys(d)
+        bb.data.update_data(d)
+
+        config_whitelist = set((d.getVar("BB_HASHCONFIG_WHITELIST", True) or "").split())
+        keys = set(key for key in iter(d) if not key.startswith("__"))
+        for key in keys:
+            if key in config_whitelist:
+                continue
+
+            value = d.getVar(key, False) or ""
+            data.update({key:value})
+
+            varflags = d.getVarFlags(key, internalflags = True)
+            if not varflags:
+                continue
+            for f in varflags:
+                if f == "_content":
+                    continue
+                data.update({'%s[%s]' % (key, f):varflags[f]})
+
+        for key in ["__BBTASKS", "__BBANONFUNCS", "__BBHANDLERS"]:
+            bb_list = d.getVar(key, False) or []
+            bb_list.sort()
+            data.update({key:str(bb_list)})
+
+            if key == "__BBANONFUNCS":
+                for i in bb_list:
+                    value = d.getVar(i, True) or ""
+                    data.update({i:value})
+
+        data_str = str([(k, data[k]) for k in sorted(data.keys())])
+        return hashlib.md5(data_str).hexdigest()
diff --git a/bitbake/lib/bb/event.py b/bitbake/lib/bb/event.py
new file mode 100644
index 0000000..366bc41
--- /dev/null
+++ b/bitbake/lib/bb/event.py
@@ -0,0 +1,668 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake 'Event' implementation
+
+Classes and functions for manipulating 'events' in the
+BitBake build tools.
+"""
+
+# Copyright (C) 2003, 2004  Chris Larson
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import os, sys
+import warnings
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+import logging
+import atexit
+import traceback
+import bb.utils
+import bb.compat
+import bb.exceptions
+
+# This is the pid for which we should generate the event. This is set when
+# the runqueue forks off.
+worker_pid = 0
+worker_fire = None
+
+logger = logging.getLogger('BitBake.Event')
+
+class Event(object):
+    """Base class for events"""
+
+    def __init__(self):
+        self.pid = worker_pid
+
+Registered        = 10
+AlreadyRegistered = 14
+
+def get_class_handlers():
+    return _handlers
+
+def set_class_handlers(h):
+    global _handlers
+    _handlers = h
+
+def clean_class_handlers():
+    return bb.compat.OrderedDict()
+
+# Internal
+_handlers = clean_class_handlers()
+_ui_handlers = {}
+_ui_logfilters = {}
+_ui_handler_seq = 0
+_event_handler_map = {}
+_catchall_handlers = {}
+_eventfilter = None
+_uiready = False
+
+def execute_handler(name, handler, event, d):
+    event.data = d
+    addedd = False
+    if 'd' not in __builtins__:
+        __builtins__['d'] = d
+        addedd = True
+    try:
+        ret = handler(event)
+    except (bb.parse.SkipRecipe, bb.BBHandledException):
+        raise
+    except Exception:
+        etype, value, tb = sys.exc_info()
+        logger.error("Execution of event handler '%s' failed" % name,
+                        exc_info=(etype, value, tb.tb_next))
+        raise
+    except SystemExit as exc:
+        if exc.code != 0:
+            logger.error("Execution of event handler '%s' failed" % name)
+        raise
+    finally:
+        del event.data
+        if addedd:
+            del __builtins__['d']
+
+def fire_class_handlers(event, d):
+    if isinstance(event, logging.LogRecord):
+        return
+
+    eid = str(event.__class__)[8:-2]
+    evt_hmap = _event_handler_map.get(eid, {})
+    for name, handler in _handlers.iteritems():
+        if name in _catchall_handlers or name in evt_hmap:
+            if _eventfilter:
+                if not _eventfilter(name, handler, event, d):
+                    continue
+            execute_handler(name, handler, event, d)
+
+ui_queue = []
+@atexit.register
+def print_ui_queue():
+    """If we're exiting before a UI has been spawned, display any queued
+    LogRecords to the console."""
+    logger = logging.getLogger("BitBake")
+    if not _uiready:
+        from bb.msg import BBLogFormatter
+        console = logging.StreamHandler(sys.stdout)
+        console.setFormatter(BBLogFormatter("%(levelname)s: %(message)s"))
+        logger.handlers = [console]
+
+        # First check to see if we have any proper messages
+        msgprint = False
+        for event in ui_queue:
+            if isinstance(event, logging.LogRecord):
+                if event.levelno > logging.DEBUG:
+                    logger.handle(event)
+                    msgprint = True
+        if msgprint:
+            return
+
+        # Nope, so just print all of the messages we have (including debug messages)
+        for event in ui_queue:
+            if isinstance(event, logging.LogRecord):
+                logger.handle(event)
+
+def fire_ui_handlers(event, d):
+    if not _uiready:
+        # No UI handlers registered yet, queue up the messages
+        ui_queue.append(event)
+        return
+
+    errors = []
+    for h in _ui_handlers:
+        #print "Sending event %s" % event
+        try:
+             if not _ui_logfilters[h].filter(event):
+                 continue
+             # We use pickle here since it better handles object instances
+             # which xmlrpc's marshaller does not. Events *must* be serializable
+             # by pickle.
+             if hasattr(_ui_handlers[h].event, "sendpickle"):
+                _ui_handlers[h].event.sendpickle((pickle.dumps(event)))
+             else:
+                _ui_handlers[h].event.send(event)
+        except:
+            errors.append(h)
+    for h in errors:
+        del _ui_handlers[h]
+
+def fire(event, d):
+    """Fire off an Event"""
+
+    # We can fire class handlers in the worker process context and this is
+    # desired so they get the task based datastore.
+    # UI handlers need to be fired in the server context so we defer this. They
+    # don't have a datastore so the datastore context isn't a problem.
+
+    fire_class_handlers(event, d)
+    if worker_fire:
+        worker_fire(event, d)
+    else:
+        fire_ui_handlers(event, d)
+
+def fire_from_worker(event, d):
+    fire_ui_handlers(event, d)
+
+noop = lambda _: None
+def register(name, handler, mask=None):
+    """Register an Event handler"""
+
+    # already registered
+    if name in _handlers:
+        return AlreadyRegistered
+
+    if handler is not None:
+        # handle string containing python code
+        if isinstance(handler, basestring):
+            tmp = "def %s(e):\n%s" % (name, handler)
+            try:
+                code = compile(tmp, "%s(e)" % name, "exec")
+            except SyntaxError:
+                logger.error("Unable to register event handler '%s':\n%s", name,
+                             ''.join(traceback.format_exc(limit=0)))
+                _handlers[name] = noop
+                return
+            env = {}
+            bb.utils.better_exec(code, env)
+            func = bb.utils.better_eval(name, env)
+            _handlers[name] = func
+        else:
+            _handlers[name] = handler
+
+        if not mask or '*' in mask:
+            _catchall_handlers[name] = True
+        else:
+            for m in mask:
+                if _event_handler_map.get(m, None) is None:
+                    _event_handler_map[m] = {}
+                _event_handler_map[m][name] = True
+
+        return Registered
+
+def remove(name, handler):
+    """Remove an Event handler"""
+    _handlers.pop(name)
+
+def set_eventfilter(func):
+    global _eventfilter
+    _eventfilter = func
+
+def register_UIHhandler(handler, mainui=False):
+    if mainui:
+        global _uiready
+        _uiready = True
+    bb.event._ui_handler_seq = bb.event._ui_handler_seq + 1
+    _ui_handlers[_ui_handler_seq] = handler
+    level, debug_domains = bb.msg.constructLogOptions()
+    _ui_logfilters[_ui_handler_seq] = UIEventFilter(level, debug_domains)
+    return _ui_handler_seq
+
+def unregister_UIHhandler(handlerNum):
+    if handlerNum in _ui_handlers:
+        del _ui_handlers[handlerNum]
+    return
+
+# Class to allow filtering of events and specific filtering of LogRecords *before* we put them over the IPC
+class UIEventFilter(object):
+    def __init__(self, level, debug_domains):
+        self.update(None, level, debug_domains)
+
+    def update(self, eventmask, level, debug_domains):
+        self.eventmask = eventmask
+        self.stdlevel = level
+        self.debug_domains = debug_domains
+
+    def filter(self, event):
+        if isinstance(event, logging.LogRecord):
+            if event.levelno >= self.stdlevel:
+                return True
+            if event.name in self.debug_domains and event.levelno >= self.debug_domains[event.name]:
+                return True
+            return False
+        eid = str(event.__class__)[8:-2]
+        if self.eventmask and eid not in self.eventmask:
+            return False
+        return True
+
+def set_UIHmask(handlerNum, level, debug_domains, mask):
+    if not handlerNum in _ui_handlers:
+        return False
+    if '*' in mask:
+        _ui_logfilters[handlerNum].update(None, level, debug_domains)
+    else:
+        _ui_logfilters[handlerNum].update(mask, level, debug_domains)
+    return True
+
+def getName(e):
+    """Returns the name of a class or class instance"""
+    if getattr(e, "__name__", None) == None:
+        return e.__class__.__name__
+    else:
+        return e.__name__
+
+class OperationStarted(Event):
+    """An operation has begun"""
+    def __init__(self, msg = "Operation Started"):
+        Event.__init__(self)
+        self.msg = msg
+
+class OperationCompleted(Event):
+    """An operation has completed"""
+    def __init__(self, total, msg = "Operation Completed"):
+        Event.__init__(self)
+        self.total = total
+        self.msg = msg
+
+class OperationProgress(Event):
+    """An operation is in progress"""
+    def __init__(self, current, total, msg = "Operation in Progress"):
+        Event.__init__(self)
+        self.current = current
+        self.total = total
+        self.msg = msg + ": %s/%s" % (current, total);
+
+class ConfigParsed(Event):
+    """Configuration Parsing Complete"""
+
+class RecipeEvent(Event):
+    def __init__(self, fn):
+        self.fn = fn
+        Event.__init__(self)
+
+class RecipePreFinalise(RecipeEvent):
+    """ Recipe Parsing Complete but not yet finialised"""
+
+class RecipeParsed(RecipeEvent):
+    """ Recipe Parsing Complete """
+
+class StampUpdate(Event):
+    """Trigger for any adjustment of the stamp files to happen"""
+
+    def __init__(self, targets, stampfns):
+        self._targets = targets
+        self._stampfns = stampfns
+        Event.__init__(self)
+
+    def getStampPrefix(self):
+        return self._stampfns
+
+    def getTargets(self):
+        return self._targets
+
+    stampPrefix = property(getStampPrefix)
+    targets = property(getTargets)
+
+class BuildBase(Event):
+    """Base class for bbmake run events"""
+
+    def __init__(self, n, p, failures = 0):
+        self._name = n
+        self._pkgs = p
+        Event.__init__(self)
+        self._failures = failures
+
+    def getPkgs(self):
+        return self._pkgs
+
+    def setPkgs(self, pkgs):
+        self._pkgs = pkgs
+
+    def getName(self):
+        return self._name
+
+    def setName(self, name):
+        self._name = name
+
+    def getCfg(self):
+        return self.data
+
+    def setCfg(self, cfg):
+        self.data = cfg
+
+    def getFailures(self):
+        """
+        Return the number of failed packages
+        """
+        return self._failures
+
+    pkgs = property(getPkgs, setPkgs, None, "pkgs property")
+    name = property(getName, setName, None, "name property")
+    cfg = property(getCfg, setCfg, None, "cfg property")
+
+
+
+
+
+class BuildStarted(BuildBase, OperationStarted):
+    """bbmake build run started"""
+    def __init__(self, n, p, failures = 0):
+        OperationStarted.__init__(self, "Building Started")
+        BuildBase.__init__(self, n, p, failures)
+
+class BuildCompleted(BuildBase, OperationCompleted):
+    """bbmake build run completed"""
+    def __init__(self, total, n, p, failures=0, interrupted=0):
+        if not failures:
+            OperationCompleted.__init__(self, total, "Building Succeeded")
+        else:
+            OperationCompleted.__init__(self, total, "Building Failed")
+        self._interrupted = interrupted
+        BuildBase.__init__(self, n, p, failures)
+
+class DiskFull(Event):
+    """Disk full case build aborted"""
+    def __init__(self, dev, type, freespace, mountpoint):
+        Event.__init__(self)
+        self._dev = dev
+        self._type = type
+        self._free = freespace
+        self._mountpoint = mountpoint
+
+class NoProvider(Event):
+    """No Provider for an Event"""
+
+    def __init__(self, item, runtime=False, dependees=None, reasons=None, close_matches=None):
+        Event.__init__(self)
+        self._item = item
+        self._runtime = runtime
+        self._dependees = dependees
+        self._reasons = reasons
+        self._close_matches = close_matches
+
+    def getItem(self):
+        return self._item
+
+    def isRuntime(self):
+        return self._runtime
+
+class MultipleProviders(Event):
+    """Multiple Providers"""
+
+    def  __init__(self, item, candidates, runtime = False):
+        Event.__init__(self)
+        self._item = item
+        self._candidates = candidates
+        self._is_runtime = runtime
+
+    def isRuntime(self):
+        """
+        Is this a runtime issue?
+        """
+        return self._is_runtime
+
+    def getItem(self):
+        """
+        The name for the to be build item
+        """
+        return self._item
+
+    def getCandidates(self):
+        """
+        Get the possible Candidates for a PROVIDER.
+        """
+        return self._candidates
+
+class ParseStarted(OperationStarted):
+    """Recipe parsing for the runqueue has begun"""
+    def __init__(self, total):
+        OperationStarted.__init__(self, "Recipe parsing Started")
+        self.total = total
+
+class ParseCompleted(OperationCompleted):
+    """Recipe parsing for the runqueue has completed"""
+    def __init__(self, cached, parsed, skipped, masked, virtuals, errors, total):
+        OperationCompleted.__init__(self, total, "Recipe parsing Completed")
+        self.cached = cached
+        self.parsed = parsed
+        self.skipped = skipped
+        self.virtuals = virtuals
+        self.masked = masked
+        self.errors = errors
+        self.sofar = cached + parsed
+
+class ParseProgress(OperationProgress):
+    """Recipe parsing progress"""
+    def __init__(self, current, total):
+        OperationProgress.__init__(self, current, total, "Recipe parsing")
+
+
+class CacheLoadStarted(OperationStarted):
+    """Loading of the dependency cache has begun"""
+    def __init__(self, total):
+        OperationStarted.__init__(self, "Loading cache Started")
+        self.total = total
+
+class CacheLoadProgress(OperationProgress):
+    """Cache loading progress"""
+    def __init__(self, current, total):
+        OperationProgress.__init__(self, current, total, "Loading cache")
+
+class CacheLoadCompleted(OperationCompleted):
+    """Cache loading is complete"""
+    def __init__(self, total, num_entries):
+        OperationCompleted.__init__(self, total, "Loading cache Completed")
+        self.num_entries = num_entries
+
+class TreeDataPreparationStarted(OperationStarted):
+    """Tree data preparation started"""
+    def __init__(self):
+        OperationStarted.__init__(self, "Preparing tree data Started")
+
+class TreeDataPreparationProgress(OperationProgress):
+    """Tree data preparation is in progress"""
+    def __init__(self, current, total):
+        OperationProgress.__init__(self, current, total, "Preparing tree data")
+
+class TreeDataPreparationCompleted(OperationCompleted):
+    """Tree data preparation completed"""
+    def __init__(self, total):
+        OperationCompleted.__init__(self, total, "Preparing tree data Completed")
+
+class DepTreeGenerated(Event):
+    """
+    Event when a dependency tree has been generated
+    """
+
+    def __init__(self, depgraph):
+        Event.__init__(self)
+        self._depgraph = depgraph
+
+class TargetsTreeGenerated(Event):
+    """
+    Event when a set of buildable targets has been generated
+    """
+    def __init__(self, model):
+        Event.__init__(self)
+        self._model = model
+
+class ReachableStamps(Event):
+    """
+    An event listing all stamps reachable after parsing
+    which the metadata may use to clean up stale data
+    """
+
+    def __init__(self, stamps):
+        Event.__init__(self)
+        self.stamps = stamps
+
+class FilesMatchingFound(Event):
+    """
+    Event when a list of files matching the supplied pattern has
+    been generated
+    """
+    def __init__(self, pattern, matches):
+        Event.__init__(self)
+        self._pattern = pattern
+        self._matches = matches
+
+class CoreBaseFilesFound(Event):
+    """
+    Event when a list of appropriate config files has been generated
+    """
+    def __init__(self, paths):
+        Event.__init__(self)
+        self._paths = paths
+
+class ConfigFilesFound(Event):
+    """
+    Event when a list of appropriate config files has been generated
+    """
+    def __init__(self, variable, values):
+        Event.__init__(self)
+        self._variable = variable
+        self._values = values
+
+class ConfigFilePathFound(Event):
+    """
+    Event when a path for a config file has been found
+    """
+    def __init__(self, path):
+        Event.__init__(self)
+        self._path = path
+
+class MsgBase(Event):
+    """Base class for messages"""
+
+    def __init__(self, msg):
+        self._message = msg
+        Event.__init__(self)
+
+class MsgDebug(MsgBase):
+    """Debug Message"""
+
+class MsgNote(MsgBase):
+    """Note Message"""
+
+class MsgWarn(MsgBase):
+    """Warning Message"""
+
+class MsgError(MsgBase):
+    """Error Message"""
+
+class MsgFatal(MsgBase):
+    """Fatal Message"""
+
+class MsgPlain(MsgBase):
+    """General output"""
+
+class LogExecTTY(Event):
+    """Send event containing program to spawn on tty of the logger"""
+    def __init__(self, msg, prog, sleep_delay, retries):
+        Event.__init__(self)
+        self.msg = msg
+        self.prog = prog
+        self.sleep_delay = sleep_delay
+        self.retries = retries
+
+class LogHandler(logging.Handler):
+    """Dispatch logging messages as bitbake events"""
+
+    def emit(self, record):
+        if record.exc_info:
+            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)
+
+    def filter(self, record):
+        record.taskpid = worker_pid
+        return True
+
+class RequestPackageInfo(Event):
+    """
+    Event to request package information
+    """
+
+class PackageInfo(Event):
+    """
+    Package information for GUI
+    """
+    def __init__(self, pkginfolist):
+        Event.__init__(self)
+        self._pkginfolist = pkginfolist
+
+class MetadataEvent(Event):
+    """
+    Generic event that target for OE-Core classes
+    to report information during asynchrous execution
+    """
+    def __init__(self, eventtype, eventdata):
+        Event.__init__(self)
+        self.type = eventtype
+        self._localdata = eventdata
+
+class SanityCheck(Event):
+    """
+    Event to run sanity checks, either raise errors or generate events as return status.
+    """
+    def __init__(self, generateevents = True):
+        Event.__init__(self)
+        self.generateevents = generateevents
+
+class SanityCheckPassed(Event):
+    """
+    Event to indicate sanity check has passed
+    """
+
+class SanityCheckFailed(Event):
+    """
+    Event to indicate sanity check has failed
+    """
+    def __init__(self, msg, network_error=False):
+        Event.__init__(self)
+        self._msg = msg
+        self._network_error = network_error
+
+class NetworkTest(Event):
+    """
+    Event to run network connectivity tests, either raise errors or generate events as return status.
+    """
+    def __init__(self, generateevents = True):
+        Event.__init__(self)
+        self.generateevents = generateevents
+
+class NetworkTestPassed(Event):
+    """
+    Event to indicate network test has passed
+    """
+
+class NetworkTestFailed(Event):
+    """
+    Event to indicate network test has failed
+    """
+
diff --git a/bitbake/lib/bb/exceptions.py b/bitbake/lib/bb/exceptions.py
new file mode 100644
index 0000000..f182c8f
--- /dev/null
+++ b/bitbake/lib/bb/exceptions.py
@@ -0,0 +1,91 @@
+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)
+
+    try:
+        if not arginfo.args:
+            return '', None
+    # There have been reports from the field of python 2.6 which doesn't 
+    # return a namedtuple here but simply a tuple so fallback gracefully if
+    # args isn't present.
+    except AttributeError:
+        return '', None
+
+    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/bitbake/lib/bb/fetch2/__init__.py b/bitbake/lib/bb/fetch2/__init__.py
new file mode 100644
index 0000000..3d53b63
--- /dev/null
+++ b/bitbake/lib/bb/fetch2/__init__.py
@@ -0,0 +1,1787 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake 'Fetch' implementations
+
+Classes for obtaining upstream sources for the
+BitBake build tools.
+"""
+
+# Copyright (C) 2003, 2004  Chris Larson
+# Copyright (C) 2012  Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Based on functions from the base bb module, Copyright 2003 Holger Schurig
+
+from __future__ import absolute_import
+from __future__ import print_function
+import os, re
+import signal
+import glob
+import logging
+import urllib
+import urlparse
+import operator
+import bb.persist_data, bb.utils
+import bb.checksum
+from bb import data
+import bb.process
+import subprocess
+
+__version__ = "2"
+_checksum_cache = bb.checksum.FileChecksumCache()
+
+logger = logging.getLogger("BitBake.Fetcher")
+
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+    logger.info("Importing cPickle failed. "
+                "Falling back to a very slow implementation.")
+
+class BBFetchException(Exception):
+    """Class all fetch exceptions inherit from"""
+    def __init__(self, message):
+         self.msg = message
+         Exception.__init__(self, message)
+
+    def __str__(self):
+         return self.msg
+
+class UntrustedUrl(BBFetchException):
+    """Exception raised when encountering a host not listed in BB_ALLOWED_NETWORKS"""
+    def __init__(self, url, message=''):
+        if message:
+            msg = message
+        else:
+            msg = "The URL: '%s' is not trusted and cannot be used" % url
+        self.url = url
+        BBFetchException.__init__(self, msg)
+        self.args = (url,)
+
+class MalformedUrl(BBFetchException):
+    """Exception raised when encountering an invalid url"""
+    def __init__(self, url, message=''):
+         if message:
+             msg = message
+         else:
+             msg = "The URL: '%s' is invalid and cannot be interpreted" % url
+         self.url = url
+         BBFetchException.__init__(self, msg)
+         self.args = (url,)
+
+class FetchError(BBFetchException):
+    """General fetcher exception when something happens incorrectly"""
+    def __init__(self, message, url = None):
+         if url:
+            msg = "Fetcher failure for URL: '%s'. %s" % (url, message)
+         else:
+            msg = "Fetcher failure: %s" % message
+         self.url = url
+         BBFetchException.__init__(self, msg)
+         self.args = (message, url)
+
+class ChecksumError(FetchError):
+    """Exception when mismatched checksum encountered"""
+    def __init__(self, message, url = None, checksum = None):
+        self.checksum = checksum
+        FetchError.__init__(self, message, url)
+
+class NoChecksumError(FetchError):
+    """Exception when no checksum is specified, but BB_STRICT_CHECKSUM is set"""
+
+class UnpackError(BBFetchException):
+    """General fetcher exception when something happens incorrectly when unpacking"""
+    def __init__(self, message, url):
+         msg = "Unpack failure for URL: '%s'. %s" % (url, message)
+         self.url = url
+         BBFetchException.__init__(self, msg)
+         self.args = (message, url)
+
+class NoMethodError(BBFetchException):
+    """Exception raised when there is no method to obtain a supplied url or set of urls"""
+    def __init__(self, url):
+         msg = "Could not find a fetcher which supports the URL: '%s'" % url
+         self.url = url
+         BBFetchException.__init__(self, msg)
+         self.args = (url,)
+
+class MissingParameterError(BBFetchException):
+    """Exception raised when a fetch method is missing a critical parameter in the url"""
+    def __init__(self, missing, url):
+         msg = "URL: '%s' is missing the required parameter '%s'" % (url, missing)
+         self.url = url
+         self.missing = missing
+         BBFetchException.__init__(self, msg)
+         self.args = (missing, url)
+
+class ParameterError(BBFetchException):
+    """Exception raised when a url cannot be proccessed due to invalid parameters."""
+    def __init__(self, message, url):
+         msg = "URL: '%s' has invalid parameters. %s" % (url, message)
+         self.url = url
+         BBFetchException.__init__(self, msg)
+         self.args = (message, url)
+
+class NetworkAccess(BBFetchException):
+    """Exception raised when network access is disabled but it is required."""
+    def __init__(self, url, cmd):
+         msg = "Network access disabled through BB_NO_NETWORK (or set indirectly due to use of BB_FETCH_PREMIRRORONLY) but access requested with command %s (for url %s)" % (cmd, url)
+         self.url = url
+         self.cmd = cmd
+         BBFetchException.__init__(self, msg)
+         self.args = (url, cmd)
+
+class NonLocalMethod(Exception):
+    def __init__(self):
+        Exception.__init__(self)
+
+
+class URI(object):
+    """
+    A class representing a generic URI, with methods for
+    accessing the URI components, and stringifies to the
+    URI.
+
+    It is constructed by calling it with a URI, or setting
+    the attributes manually:
+
+     uri = URI("http://example.com/")
+
+     uri = URI()
+     uri.scheme = 'http'
+     uri.hostname = 'example.com'
+     uri.path = '/'
+
+    It has the following attributes:
+
+      * scheme (read/write)
+      * userinfo (authentication information) (read/write)
+        * username (read/write)
+        * password (read/write)
+
+        Note, password is deprecated as of RFC 3986.
+
+      * hostname (read/write)
+      * port (read/write)
+      * hostport (read only)
+        "hostname:port", if both are set, otherwise just "hostname"
+      * path (read/write)
+      * path_quoted (read/write)
+        A URI quoted version of path
+      * params (dict) (read/write)
+      * query (dict) (read/write)
+      * relative (bool) (read only)
+        True if this is a "relative URI", (e.g. file:foo.diff)
+
+    It stringifies to the URI itself.
+
+    Some notes about relative URIs: while it's specified that
+    a URI beginning with <scheme>:// should either be directly
+    followed by a hostname or a /, the old URI handling of the
+    fetch2 library did not comform to this. Therefore, this URI
+    class has some kludges to make sure that URIs are parsed in
+    a way comforming to bitbake's current usage. This URI class
+    supports the following:
+
+     file:relative/path.diff (IETF compliant)
+     git:relative/path.git (IETF compliant)
+     git:///absolute/path.git (IETF compliant)
+     file:///absolute/path.diff (IETF compliant)
+
+     file://relative/path.diff (not IETF compliant)
+
+    But it does not support the following:
+
+     file://hostname/absolute/path.diff (would be IETF compliant)
+
+    Note that the last case only applies to a list of
+    "whitelisted" schemes (currently only file://), that requires
+    its URIs to not have a network location.
+    """
+
+    _relative_schemes = ['file', 'git']
+    _netloc_forbidden = ['file']
+
+    def __init__(self, uri=None):
+        self.scheme = ''
+        self.userinfo = ''
+        self.hostname = ''
+        self.port = None
+        self._path = ''
+        self.params = {}
+        self.query = {}
+        self.relative = False
+
+        if not uri:
+            return
+
+        # We hijack the URL parameters, since the way bitbake uses
+        # them are not quite RFC compliant.
+        uri, param_str = (uri.split(";", 1) + [None])[:2]
+
+        urlp = urlparse.urlparse(uri)
+        self.scheme = urlp.scheme
+
+        reparse = 0
+
+        # Coerce urlparse to make URI scheme use netloc
+        if not self.scheme in urlparse.uses_netloc:
+            urlparse.uses_params.append(self.scheme)
+            reparse = 1
+
+        # Make urlparse happy(/ier) by converting local resources
+        # to RFC compliant URL format. E.g.:
+        #   file://foo.diff -> file:foo.diff
+        if urlp.scheme in self._netloc_forbidden:
+            uri = re.sub("(?<=:)//(?!/)", "", uri, 1)
+            reparse = 1
+
+        if reparse:
+            urlp = urlparse.urlparse(uri)
+
+        # Identify if the URI is relative or not
+        if urlp.scheme in self._relative_schemes and \
+           re.compile("^\w+:(?!//)").match(uri):
+            self.relative = True
+
+        if not self.relative:
+            self.hostname = urlp.hostname or ''
+            self.port = urlp.port
+
+            self.userinfo += urlp.username or ''
+
+            if urlp.password:
+                self.userinfo += ':%s' % urlp.password
+
+        self.path = urllib.unquote(urlp.path)
+
+        if param_str:
+            self.params = self._param_str_split(param_str, ";")
+        if urlp.query:
+            self.query = self._param_str_split(urlp.query, "&")
+
+    def __str__(self):
+        userinfo = self.userinfo
+        if userinfo:
+            userinfo += '@'
+
+        return "%s:%s%s%s%s%s%s" % (
+            self.scheme,
+            '' if self.relative else '//',
+            userinfo,
+            self.hostport,
+            self.path_quoted,
+            self._query_str(),
+            self._param_str())
+
+    def _param_str(self):
+        return (
+            ''.join([';', self._param_str_join(self.params, ";")])
+            if self.params else '')
+
+    def _query_str(self):
+        return (
+            ''.join(['?', self._param_str_join(self.query, "&")])
+            if self.query else '')
+
+    def _param_str_split(self, string, elmdelim, kvdelim="="):
+        ret = {}
+        for k, v in [x.split(kvdelim, 1) for x in string.split(elmdelim)]:
+            ret[k] = v
+        return ret
+
+    def _param_str_join(self, dict_, elmdelim, kvdelim="="):
+        return elmdelim.join([kvdelim.join([k, v]) for k, v in dict_.items()])
+
+    @property
+    def hostport(self):
+        if not self.port:
+            return self.hostname
+        return "%s:%d" % (self.hostname, self.port)
+
+    @property
+    def path_quoted(self):
+        return urllib.quote(self.path)
+
+    @path_quoted.setter
+    def path_quoted(self, path):
+        self.path = urllib.unquote(path)
+
+    @property
+    def path(self):
+        return self._path
+
+    @path.setter
+    def path(self, path):
+        self._path = path
+
+        if re.compile("^/").match(path):
+            self.relative = False
+        else:
+            self.relative = True
+
+    @property
+    def username(self):
+        if self.userinfo:
+            return (self.userinfo.split(":", 1))[0]
+        return ''
+
+    @username.setter
+    def username(self, username):
+        password = self.password
+        self.userinfo = username
+        if password:
+            self.userinfo += ":%s" % password
+
+    @property
+    def password(self):
+        if self.userinfo and ":" in self.userinfo:
+            return (self.userinfo.split(":", 1))[1]
+        return ''
+
+    @password.setter
+    def password(self, password):
+        self.userinfo = "%s:%s" % (self.username, password)
+
+def decodeurl(url):
+    """Decodes an URL into the tokens (scheme, network location, path,
+    user, password, parameters).
+    """
+
+    m = re.compile('(?P<type>[^:]*)://((?P<user>[^/]+)@)?(?P<location>[^;]+)(;(?P<parm>.*))?').match(url)
+    if not m:
+        raise MalformedUrl(url)
+
+    type = m.group('type')
+    location = m.group('location')
+    if not location:
+        raise MalformedUrl(url)
+    user = m.group('user')
+    parm = m.group('parm')
+
+    locidx = location.find('/')
+    if locidx != -1 and type.lower() != 'file':
+        host = location[:locidx]
+        path = location[locidx:]
+    else:
+        host = ""
+        path = location
+    if user:
+        m = re.compile('(?P<user>[^:]+)(:?(?P<pswd>.*))').match(user)
+        if m:
+            user = m.group('user')
+            pswd = m.group('pswd')
+    else:
+        user = ''
+        pswd = ''
+
+    p = {}
+    if parm:
+        for s in parm.split(';'):
+            if s:
+                if not '=' in s:
+                    raise MalformedUrl(url, "The URL: '%s' is invalid: parameter %s does not specify a value (missing '=')" % (url, s))
+                s1, s2 = s.split('=')
+                p[s1] = s2
+
+    return type, host, urllib.unquote(path), user, pswd, p
+
+def encodeurl(decoded):
+    """Encodes a URL from tokens (scheme, network location, path,
+    user, password, parameters).
+    """
+
+    type, host, path, user, pswd, p = decoded
+
+    if not path:
+        raise MissingParameterError('path', "encoded from the data %s" % str(decoded))
+    if not type:
+        raise MissingParameterError('type', "encoded from the data %s" % str(decoded))
+    url = '%s://' % type
+    if user and type != "file":
+        url += "%s" % user
+        if pswd:
+            url += ":%s" % pswd
+        url += "@"
+    if host and type != "file":
+        url += "%s" % host
+    # Standardise path to ensure comparisons work
+    while '//' in path:
+        path = path.replace("//", "/")
+    url += "%s" % urllib.quote(path)
+    if p:
+        for parm in p:
+            url += ";%s=%s" % (parm, p[parm])
+
+    return url
+
+def uri_replace(ud, uri_find, uri_replace, replacements, d):
+    if not ud.url or not uri_find or not uri_replace:
+        logger.error("uri_replace: passed an undefined value, not replacing")
+        return None
+    uri_decoded = list(decodeurl(ud.url))
+    uri_find_decoded = list(decodeurl(uri_find))
+    uri_replace_decoded = list(decodeurl(uri_replace))
+    logger.debug(2, "For url %s comparing %s to %s" % (uri_decoded, uri_find_decoded, uri_replace_decoded))
+    result_decoded = ['', '', '', '', '', {}]
+    for loc, i in enumerate(uri_find_decoded):
+        result_decoded[loc] = uri_decoded[loc]
+        regexp = i
+        if loc == 0 and regexp and not regexp.endswith("$"):
+            # Leaving the type unanchored can mean "https" matching "file" can become "files"
+            # which is clearly undesirable.
+            regexp += "$"
+        if loc == 5:
+            # Handle URL parameters
+            if i:
+                # Any specified URL parameters must match
+                for k in uri_replace_decoded[loc]:
+                    if uri_decoded[loc][k] != uri_replace_decoded[loc][k]:
+                        return None
+            # Overwrite any specified replacement parameters
+            for k in uri_replace_decoded[loc]:
+                for l in replacements:
+                    uri_replace_decoded[loc][k] = uri_replace_decoded[loc][k].replace(l, replacements[l])
+                result_decoded[loc][k] = uri_replace_decoded[loc][k]
+        elif (re.match(regexp, uri_decoded[loc])):
+            if not uri_replace_decoded[loc]:
+                result_decoded[loc] = ""    
+            else:
+                for k in replacements:
+                    uri_replace_decoded[loc] = uri_replace_decoded[loc].replace(k, replacements[k])
+                #bb.note("%s %s %s" % (regexp, uri_replace_decoded[loc], uri_decoded[loc]))
+                result_decoded[loc] = re.sub(regexp, uri_replace_decoded[loc], uri_decoded[loc])
+            if loc == 2:
+                # Handle path manipulations
+                basename = None
+                if uri_decoded[0] != uri_replace_decoded[0] and ud.mirrortarball:
+                    # If the source and destination url types differ, must be a mirrortarball mapping
+                    basename = os.path.basename(ud.mirrortarball)
+                    # Kill parameters, they make no sense for mirror tarballs
+                    uri_decoded[5] = {}
+                elif ud.localpath and ud.method.supports_checksum(ud):
+                    basename = os.path.basename(ud.localpath)
+                if basename and not result_decoded[loc].endswith(basename):
+                    result_decoded[loc] = os.path.join(result_decoded[loc], basename)
+        else:
+            return None
+    result = encodeurl(result_decoded)
+    if result == ud.url:
+        return None
+    logger.debug(2, "For url %s returning %s" % (ud.url, result))
+    return result
+
+methods = []
+urldata_cache = {}
+saved_headrevs = {}
+
+def fetcher_init(d):
+    """
+    Called to initialize the fetchers once the configuration data is known.
+    Calls before this must not hit the cache.
+    """
+    # When to drop SCM head revisions controlled by user policy
+    srcrev_policy = d.getVar('BB_SRCREV_POLICY', True) or "clear"
+    if srcrev_policy == "cache":
+        logger.debug(1, "Keeping SRCREV cache due to cache policy of: %s", srcrev_policy)
+    elif srcrev_policy == "clear":
+        logger.debug(1, "Clearing SRCREV cache due to cache policy of: %s", srcrev_policy)
+        revs = bb.persist_data.persist('BB_URI_HEADREVS', d)
+        try:
+            bb.fetch2.saved_headrevs = revs.items()
+        except:
+            pass
+        revs.clear()
+    else:
+        raise FetchError("Invalid SRCREV cache policy of: %s" % srcrev_policy)
+
+    _checksum_cache.init_cache(d)
+
+    for m in methods:
+        if hasattr(m, "init"):
+            m.init(d)
+
+def fetcher_parse_save(d):
+    _checksum_cache.save_extras(d)
+
+def fetcher_parse_done(d):
+    _checksum_cache.save_merge(d)
+
+def fetcher_compare_revisions(d):
+    """
+    Compare the revisions in the persistant cache with current values and
+    return true/false on whether they've changed.
+    """
+
+    data = bb.persist_data.persist('BB_URI_HEADREVS', d).items()
+    data2 = bb.fetch2.saved_headrevs
+
+    changed = False
+    for key in data:
+        if key not in data2 or data2[key] != data[key]:
+            logger.debug(1, "%s changed", key)
+            changed = True
+            return True
+        else:
+            logger.debug(2, "%s did not change", key)
+    return False
+
+def mirror_from_string(data):
+    return [ i.split() for i in (data or "").replace('\\n','\n').split('\n') if i ]
+
+def verify_checksum(ud, d, precomputed={}):
+    """
+    verify the MD5 and SHA256 checksum for downloaded src
+
+    Raises a FetchError if one or both of the SRC_URI checksums do not match
+    the downloaded file, or if BB_STRICT_CHECKSUM is set and there are no
+    checksums specified.
+
+    Returns a dict of checksums that can be stored in a done stamp file and
+    passed in as precomputed parameter in a later call to avoid re-computing
+    the checksums from the file. This allows verifying the checksums of the
+    file against those in the recipe each time, rather than only after
+    downloading. See https://bugzilla.yoctoproject.org/show_bug.cgi?id=5571.
+    """
+
+    _MD5_KEY = "md5"
+    _SHA256_KEY = "sha256"
+
+    if ud.ignore_checksums or not ud.method.supports_checksum(ud):
+        return {}
+
+    if _MD5_KEY in precomputed:
+        md5data = precomputed[_MD5_KEY]
+    else:
+        md5data = bb.utils.md5_file(ud.localpath)
+
+    if _SHA256_KEY in precomputed:
+        sha256data = precomputed[_SHA256_KEY]
+    else:
+        sha256data = bb.utils.sha256_file(ud.localpath)
+
+    if ud.method.recommends_checksum(ud):
+        # If strict checking enabled and neither sum defined, raise error
+        strict = d.getVar("BB_STRICT_CHECKSUM", True) or "0"
+        if (strict == "1") and not (ud.md5_expected or ud.sha256_expected):
+            logger.error('No checksum specified for %s, please add at least one to the recipe:\n'
+                             'SRC_URI[%s] = "%s"\nSRC_URI[%s] = "%s"' %
+                             (ud.localpath, ud.md5_name, md5data,
+                              ud.sha256_name, sha256data))
+            raise NoChecksumError('Missing SRC_URI checksum', ud.url)
+
+        # Log missing sums so user can more easily add them
+        if not ud.md5_expected:
+            logger.warn('Missing md5 SRC_URI checksum for %s, consider adding to the recipe:\n'
+                        'SRC_URI[%s] = "%s"',
+                        ud.localpath, ud.md5_name, md5data)
+
+        if not ud.sha256_expected:
+            logger.warn('Missing sha256 SRC_URI checksum for %s, consider adding to the recipe:\n'
+                        'SRC_URI[%s] = "%s"',
+                        ud.localpath, ud.sha256_name, sha256data)
+
+    md5mismatch = False
+    sha256mismatch = False
+
+    if ud.md5_expected != md5data:
+        md5mismatch = True
+
+    if ud.sha256_expected != sha256data:
+        sha256mismatch = True
+
+    # We want to alert the user if a checksum is defined in the recipe but
+    # it does not match.
+    msg = ""
+    mismatch = False
+    if md5mismatch and ud.md5_expected:
+        msg = msg + "\nFile: '%s' has %s checksum %s when %s was expected" % (ud.localpath, 'md5', md5data, ud.md5_expected)
+        mismatch = True;
+
+    if sha256mismatch and ud.sha256_expected:
+        msg = msg + "\nFile: '%s' has %s checksum %s when %s was expected" % (ud.localpath, 'sha256', sha256data, ud.sha256_expected)
+        mismatch = True;
+
+    if mismatch:
+        msg = msg + '\nIf this change is expected (e.g. you have upgraded to a new version without updating the checksums) then you can use these lines within the recipe:\nSRC_URI[%s] = "%s"\nSRC_URI[%s] = "%s"\nOtherwise you should retry the download and/or check with upstream to determine if the file has become corrupted or otherwise unexpectedly modified.\n' % (ud.md5_name, md5data, ud.sha256_name, sha256data)
+
+    if len(msg):
+        raise ChecksumError('Checksum mismatch!%s' % msg, ud.url, md5data)
+
+    return {
+        _MD5_KEY: md5data,
+        _SHA256_KEY: sha256data
+    }
+
+
+def verify_donestamp(ud, d, origud=None):
+    """
+    Check whether the done stamp file has the right checksums (if the fetch
+    method supports them). If it doesn't, delete the done stamp and force
+    a re-download.
+
+    Returns True, if the donestamp exists and is valid, False otherwise. When
+    returning False, any existing done stamps are removed.
+    """
+    if not os.path.exists(ud.donestamp):
+        return False
+
+    if (not ud.method.supports_checksum(ud) or
+        (origud and not origud.method.supports_checksum(origud))):
+        # done stamp exists, checksums not supported; assume the local file is
+        # current
+        return True
+
+    if not os.path.exists(ud.localpath):
+        # done stamp exists, but the downloaded file does not; the done stamp
+        # must be incorrect, re-trigger the download
+        bb.utils.remove(ud.donestamp)
+        return False
+
+    precomputed_checksums = {}
+    # Only re-use the precomputed checksums if the donestamp is newer than the
+    # file. Do not rely on the mtime of directories, though. If ud.localpath is
+    # a directory, there will probably not be any checksums anyway.
+    if (os.path.isdir(ud.localpath) or
+            os.path.getmtime(ud.localpath) < os.path.getmtime(ud.donestamp)):
+        try:
+            with open(ud.donestamp, "rb") as cachefile:
+                pickled = pickle.Unpickler(cachefile)
+                precomputed_checksums.update(pickled.load())
+        except Exception as e:
+            # Avoid the warnings on the upgrade path from emtpy done stamp
+            # files to those containing the checksums.
+            if not isinstance(e, EOFError):
+                # Ignore errors, they aren't fatal
+                logger.warn("Couldn't load checksums from donestamp %s: %s "
+                            "(msg: %s)" % (ud.donestamp, type(e).__name__,
+                                           str(e)))
+
+    try:
+        checksums = verify_checksum(ud, d, precomputed_checksums)
+        # If the cache file did not have the checksums, compute and store them
+        # as an upgrade path from the previous done stamp file format.
+        if checksums != precomputed_checksums:
+            with open(ud.donestamp, "wb") as cachefile:
+                p = pickle.Pickler(cachefile, pickle.HIGHEST_PROTOCOL)
+                p.dump(checksums)
+        return True
+    except ChecksumError as e:
+        # Checksums failed to verify, trigger re-download and remove the
+        # incorrect stamp file.
+        logger.warn("Checksum mismatch for local file %s\n"
+                    "Cleaning and trying again." % ud.localpath)
+        rename_bad_checksum(ud, e.checksum)
+        bb.utils.remove(ud.donestamp)
+    return False
+
+
+def update_stamp(ud, d):
+    """
+        donestamp is file stamp indicating the whole fetching is done
+        this function update the stamp after verifying the checksum
+    """
+    if os.path.exists(ud.donestamp):
+        # Touch the done stamp file to show active use of the download
+        try:
+            os.utime(ud.donestamp, None)
+        except:
+            # Errors aren't fatal here
+            pass
+    else:
+        checksums = verify_checksum(ud, d)
+        # Store the checksums for later re-verification against the recipe
+        with open(ud.donestamp, "wb") as cachefile:
+            p = pickle.Pickler(cachefile, pickle.HIGHEST_PROTOCOL)
+            p.dump(checksums)
+
+def subprocess_setup():
+    # Python installs a SIGPIPE handler by default. This is usually not what
+    # non-Python subprocesses expect.
+    # SIGPIPE errors are known issues with gzip/bash
+    signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+
+def get_autorev(d):
+    #  only not cache src rev in autorev case
+    if d.getVar('BB_SRCREV_POLICY', True) != "cache":
+        d.setVar('__BB_DONT_CACHE', '1')
+    return "AUTOINC"
+
+def get_srcrev(d, method_name='sortable_revision'):
+    """
+    Return the revsion string, usually for use in the version string (PV) of the current package
+    Most packages usually only have one SCM so we just pass on the call.
+    In the multi SCM case, we build a value based on SRCREV_FORMAT which must
+    have been set.
+
+    The idea here is that we put the string "AUTOINC+" into return value if the revisions are not 
+    incremental, other code is then responsible for turning that into an increasing value (if needed)
+
+    A method_name can be supplied to retrieve an alternatively formatted revision from a fetcher, if
+    that fetcher provides a method with the given name and the same signature as sortable_revision.
+    """
+
+    scms = []
+    fetcher = Fetch(d.getVar('SRC_URI', True).split(), d)
+    urldata = fetcher.ud
+    for u in urldata:
+        if urldata[u].method.supports_srcrev():
+            scms.append(u)
+
+    if len(scms) == 0:
+        raise FetchError("SRCREV was used yet no valid SCM was found in SRC_URI")
+
+    if len(scms) == 1 and len(urldata[scms[0]].names) == 1:
+        autoinc, rev = getattr(urldata[scms[0]].method, method_name)(urldata[scms[0]], d, urldata[scms[0]].names[0])
+        if len(rev) > 10:
+            rev = rev[:10]
+        if autoinc:
+            return "AUTOINC+" + rev
+        return rev
+
+    #
+    # Mutiple SCMs are in SRC_URI so we resort to SRCREV_FORMAT
+    #
+    format = d.getVar('SRCREV_FORMAT', True)
+    if not format:
+        raise FetchError("The SRCREV_FORMAT variable must be set when multiple SCMs are used.")
+
+    seenautoinc = False
+    for scm in scms:
+        ud = urldata[scm]
+        for name in ud.names:
+            autoinc, rev = getattr(ud.method, method_name)(ud, d, name)
+            seenautoinc = seenautoinc or autoinc
+            if len(rev) > 10:
+                rev = rev[:10]
+            format = format.replace(name, rev)
+    if seenautoinc:
+       format = "AUTOINC+" + format
+
+    return format
+
+def localpath(url, d):
+    fetcher = bb.fetch2.Fetch([url], d)
+    return fetcher.localpath(url)
+
+def runfetchcmd(cmd, d, quiet=False, cleanup=None):
+    """
+    Run cmd returning the command output
+    Raise an error if interrupted or cmd fails
+    Optionally echo command output to stdout
+    Optionally remove the files/directories listed in cleanup upon failure
+    """
+
+    # Need to export PATH as binary could be in metadata paths
+    # rather than host provided
+    # Also include some other variables.
+    # FIXME: Should really include all export varaiables?
+    exportvars = ['HOME', 'PATH',
+                  'HTTP_PROXY', 'http_proxy',
+                  'HTTPS_PROXY', 'https_proxy',
+                  'FTP_PROXY', 'ftp_proxy',
+                  'FTPS_PROXY', 'ftps_proxy',
+                  'NO_PROXY', 'no_proxy',
+                  'ALL_PROXY', 'all_proxy',
+                  'GIT_PROXY_COMMAND',
+                  'GIT_SSL_CAINFO',
+                  'GIT_SMART_HTTP',
+                  'SSH_AUTH_SOCK', 'SSH_AGENT_PID',
+                  'SOCKS5_USER', 'SOCKS5_PASSWD']
+
+    if not cleanup:
+        cleanup = []
+
+    for var in exportvars:
+        val = d.getVar(var, True)
+        if val:
+            cmd = 'export ' + var + '=\"%s\"; %s' % (val, cmd)
+
+    logger.debug(1, "Running %s", cmd)
+
+    success = False
+    error_message = ""
+
+    try:
+        (output, errors) = bb.process.run(cmd, shell=True, stderr=subprocess.PIPE)
+        success = True
+    except bb.process.NotFoundError as e:
+        error_message = "Fetch command %s" % (e.command)
+    except bb.process.ExecutionError as e:
+        if e.stdout:
+            output = "output:\n%s\n%s" % (e.stdout, e.stderr)
+        elif e.stderr:
+            output = "output:\n%s" % e.stderr
+        else:
+            output = "no output"
+        error_message = "Fetch command failed with exit code %s, %s" % (e.exitcode, output)
+    except bb.process.CmdError as e:
+        error_message = "Fetch command %s could not be run:\n%s" % (e.command, e.msg)
+    if not success:
+        for f in cleanup:
+            try:
+                bb.utils.remove(f, True)
+            except OSError:
+                pass
+
+        raise FetchError(error_message)
+
+    return output
+
+def check_network_access(d, info = "", url = None):
+    """
+    log remote network access, and error if BB_NO_NETWORK is set
+    """
+    if d.getVar("BB_NO_NETWORK", True) == "1":
+        raise NetworkAccess(url, info)
+    else:
+        logger.debug(1, "Fetcher accessed the network with the command %s" % info)
+
+def build_mirroruris(origud, mirrors, ld):
+    uris = []
+    uds = []
+
+    replacements = {}
+    replacements["TYPE"] = origud.type
+    replacements["HOST"] = origud.host
+    replacements["PATH"] = origud.path
+    replacements["BASENAME"] = origud.path.split("/")[-1]
+    replacements["MIRRORNAME"] = origud.host.replace(':','.') + origud.path.replace('/', '.').replace('*', '.')
+
+    def adduri(ud, uris, uds):
+        for line in mirrors:
+            try:
+                (find, replace) = line
+            except ValueError:
+                continue
+            newuri = uri_replace(ud, find, replace, replacements, ld)
+            if not newuri or newuri in uris or newuri == origud.url:
+                continue
+
+            if not trusted_network(ld, newuri):
+                logger.debug(1, "Mirror %s not in the list of trusted networks, skipping" %  (newuri))
+                continue
+
+            try:
+                newud = FetchData(newuri, ld)
+                newud.setup_localpath(ld)
+            except bb.fetch2.BBFetchException as e:
+                logger.debug(1, "Mirror fetch failure for url %s (original url: %s)" % (newuri, origud.url))
+                logger.debug(1, str(e))
+                try:
+                    # setup_localpath of file:// urls may fail, we should still see 
+                    # if mirrors of the url exist
+                    adduri(newud, uris, uds)
+                except UnboundLocalError:
+                    pass
+                continue   
+            uris.append(newuri)
+            uds.append(newud)
+
+            adduri(newud, uris, uds)
+
+    adduri(origud, uris, uds)
+
+    return uris, uds
+
+def rename_bad_checksum(ud, suffix):
+    """
+    Renames files to have suffix from parameter
+    """
+
+    if ud.localpath is None:
+        return
+
+    new_localpath = "%s_bad-checksum_%s" % (ud.localpath, suffix)
+    bb.warn("Renaming %s to %s" % (ud.localpath, new_localpath))
+    bb.utils.movefile(ud.localpath, new_localpath)
+
+
+def try_mirror_url(fetch, origud, ud, ld, check = False):
+    # Return of None or a value means we're finished
+    # False means try another url
+    try:
+        if check:
+            found = ud.method.checkstatus(fetch, ud, ld)
+            if found:
+                return found
+            return False
+
+        os.chdir(ld.getVar("DL_DIR", True))
+
+        if not verify_donestamp(ud, ld, origud) or ud.method.need_update(ud, ld):
+            ud.method.download(ud, ld)
+            if hasattr(ud.method,"build_mirror_data"):
+                ud.method.build_mirror_data(ud, ld)
+
+        if not ud.localpath or not os.path.exists(ud.localpath):
+            return False
+
+        if ud.localpath == origud.localpath:
+            return ud.localpath
+
+        # We may be obtaining a mirror tarball which needs further processing by the real fetcher
+        # If that tarball is a local file:// we need to provide a symlink to it
+        dldir = ld.getVar("DL_DIR", True)
+        if origud.mirrortarball and os.path.basename(ud.localpath) == os.path.basename(origud.mirrortarball) \
+                and os.path.basename(ud.localpath) != os.path.basename(origud.localpath):
+            # Create donestamp in old format to avoid triggering a re-download
+            bb.utils.mkdirhier(os.path.dirname(ud.donestamp))
+            open(ud.donestamp, 'w').close()
+            dest = os.path.join(dldir, os.path.basename(ud.localpath))
+            if not os.path.exists(dest):
+                os.symlink(ud.localpath, dest)
+            if not verify_donestamp(origud, ld) or origud.method.need_update(origud, ld):
+                origud.method.download(origud, ld)
+                if hasattr(origud.method,"build_mirror_data"):
+                    origud.method.build_mirror_data(origud, ld)
+            return ud.localpath
+        # Otherwise the result is a local file:// and we symlink to it
+        if not os.path.exists(origud.localpath):
+            if os.path.islink(origud.localpath):
+                # Broken symbolic link
+                os.unlink(origud.localpath)
+
+            os.symlink(ud.localpath, origud.localpath)
+        update_stamp(origud, ld)
+        return ud.localpath
+
+    except bb.fetch2.NetworkAccess:
+        raise
+
+    except bb.fetch2.BBFetchException as e:
+        if isinstance(e, ChecksumError):
+            logger.warn("Mirror checksum failure for url %s (original url: %s)\nCleaning and trying again." % (ud.url, origud.url))
+            logger.warn(str(e))
+            rename_bad_checksum(ud, e.checksum)
+        elif isinstance(e, NoChecksumError):
+            raise
+        else:
+            logger.debug(1, "Mirror fetch failure for url %s (original url: %s)" % (ud.url, origud.url))
+            logger.debug(1, str(e))
+        try:
+            ud.method.clean(ud, ld)
+        except UnboundLocalError:
+            pass
+        return False
+
+def try_mirrors(fetch, d, origud, mirrors, check = False):
+    """
+    Try to use a mirrored version of the sources.
+    This method will be automatically called before the fetchers go.
+
+    d Is a bb.data instance
+    uri is the original uri we're trying to download
+    mirrors is the list of mirrors we're going to try
+    """
+    ld = d.createCopy()
+
+    uris, uds = build_mirroruris(origud, mirrors, ld)
+
+    for index, uri in enumerate(uris):
+        ret = try_mirror_url(fetch, origud, uds[index], ld, check)
+        if ret != False:
+            return ret
+    return None
+
+def trusted_network(d, url):
+    """
+    Use a trusted url during download if networking is enabled and
+    BB_ALLOWED_NETWORKS is set globally or for a specific recipe.
+    Note: modifies SRC_URI & mirrors.
+    """
+    if d.getVar('BB_NO_NETWORK', True) == "1":
+        return True
+
+    pkgname = d.expand(d.getVar('PN', False))
+    trusted_hosts = d.getVarFlag('BB_ALLOWED_NETWORKS', pkgname)
+
+    if not trusted_hosts:
+        trusted_hosts = d.getVar('BB_ALLOWED_NETWORKS', True)
+
+    # Not enabled.
+    if not trusted_hosts:
+        return True
+
+    scheme, network, path, user, passwd, param = decodeurl(url)
+
+    if not network:
+        return True
+
+    network = network.lower()
+
+    for host in trusted_hosts.split(" "):
+        host = host.lower()
+        if host.startswith("*.") and ("." + network).endswith(host[1:]):
+            return True
+        if host == network:
+            return True
+
+    return False
+
+def srcrev_internal_helper(ud, d, name):
+    """
+    Return:
+        a) a source revision if specified
+        b) latest revision if SRCREV="AUTOINC"
+        c) None if not specified
+    """
+
+    srcrev = None
+    pn = d.getVar("PN", True)
+    attempts = []
+    if name != '' and pn:
+        attempts.append("SRCREV_%s_pn-%s" % (name, pn))
+    if name != '':
+        attempts.append("SRCREV_%s" % name)
+    if pn:
+        attempts.append("SRCREV_pn-%s" % pn)
+    attempts.append("SRCREV")
+
+    for a in attempts:
+        srcrev = d.getVar(a, True)              
+        if srcrev and srcrev != "INVALID":
+            break
+
+    if 'rev' in ud.parm and 'tag' in ud.parm:
+        raise FetchError("Please specify a ;rev= parameter or a ;tag= parameter in the url %s but not both." % (ud.url))
+
+    if 'rev' in ud.parm or 'tag' in ud.parm:
+        if 'rev' in ud.parm:
+            parmrev = ud.parm['rev']
+        else:
+            parmrev = ud.parm['tag']
+        if srcrev == "INVALID" or not srcrev:
+            return parmrev
+        if srcrev != parmrev:
+            raise FetchError("Conflicting revisions (%s from SRCREV and %s from the url) found, please spcify one valid value" % (srcrev, parmrev))
+        return parmrev
+
+    if srcrev == "INVALID" or not srcrev:
+        raise FetchError("Please set a valid SRCREV for url %s (possible key names are %s, or use a ;rev=X URL parameter)" % (str(attempts), ud.url), ud.url)
+    if srcrev == "AUTOINC":
+        srcrev = ud.method.latest_revision(ud, d, name)
+
+    return srcrev
+
+def get_checksum_file_list(d):
+    """ Get a list of files checksum in SRC_URI
+
+    Returns the resolved local paths of all local file entries in
+    SRC_URI as a space-separated string
+    """
+    fetch = Fetch([], d, cache = False, localonly = True)
+
+    dl_dir = d.getVar('DL_DIR', True)
+    filelist = []
+    for u in fetch.urls:
+        ud = fetch.ud[u]
+
+        if ud and isinstance(ud.method, local.Local):
+            paths = ud.method.localpaths(ud, d)
+            for f in paths:
+                pth = ud.decodedurl
+                if '*' in pth:
+                    f = os.path.join(os.path.abspath(f), pth)
+                if f.startswith(dl_dir):
+                    # The local fetcher's behaviour is to return a path under DL_DIR if it couldn't find the file anywhere else
+                    if os.path.exists(f):
+                        bb.warn("Getting checksum for %s SRC_URI entry %s: file not found except in DL_DIR" % (d.getVar('PN', True), os.path.basename(f)))
+                    else:
+                        bb.warn("Unable to get checksum for %s SRC_URI entry %s: file could not be found" % (d.getVar('PN', True), os.path.basename(f)))
+                filelist.append(f + ":" + str(os.path.exists(f)))
+
+    return " ".join(filelist)
+
+def get_file_checksums(filelist, pn):
+    """Get a list of the checksums for a list of local files
+
+    Returns the checksums for a list of local files, caching the results as
+    it proceeds
+
+    """
+
+    def checksum_file(f):
+        try:
+            checksum = _checksum_cache.get_checksum(f)
+        except OSError as e:
+            bb.warn("Unable to get checksum for %s SRC_URI entry %s: %s" % (pn, os.path.basename(f), e))
+            return None
+        return checksum
+
+    def checksum_dir(pth):
+        # Handle directories recursively
+        dirchecksums = []
+        for root, dirs, files in os.walk(pth):
+            for name in files:
+                fullpth = os.path.join(root, name)
+                checksum = checksum_file(fullpth)
+                if checksum:
+                    dirchecksums.append((fullpth, checksum))
+        return dirchecksums
+
+    checksums = []
+    for pth in filelist.split():
+        exist = pth.split(":")[1]
+        if exist == "False":
+            continue
+        pth = pth.split(":")[0]
+        if '*' in pth:
+            # Handle globs
+            for f in glob.glob(pth):
+                if os.path.isdir(f):
+                    checksums.extend(checksum_dir(f))
+                else:
+                    checksum = checksum_file(f)
+                    checksums.append((f, checksum))
+        elif os.path.isdir(pth):
+            checksums.extend(checksum_dir(pth))
+        else:
+            checksum = checksum_file(pth)
+            checksums.append((pth, checksum))
+
+    checksums.sort(key=operator.itemgetter(1))
+    return checksums
+
+
+class FetchData(object):
+    """
+    A class which represents the fetcher state for a given URI.
+    """
+    def __init__(self, url, d, localonly = False):
+        # localpath is the location of a downloaded result. If not set, the file is local.
+        self.donestamp = None
+        self.localfile = ""
+        self.localpath = None
+        self.lockfile = None
+        self.mirrortarball = None
+        self.basename = None
+        self.basepath = None
+        (self.type, self.host, self.path, self.user, self.pswd, self.parm) = decodeurl(data.expand(url, d))
+        self.date = self.getSRCDate(d)
+        self.url = url
+        if not self.user and "user" in self.parm:
+            self.user = self.parm["user"]
+        if not self.pswd and "pswd" in self.parm:
+            self.pswd = self.parm["pswd"]
+        self.setup = False
+
+        if "name" in self.parm:
+            self.md5_name = "%s.md5sum" % self.parm["name"]
+            self.sha256_name = "%s.sha256sum" % self.parm["name"]
+        else:
+            self.md5_name = "md5sum"
+            self.sha256_name = "sha256sum"
+        if self.md5_name in self.parm:
+            self.md5_expected = self.parm[self.md5_name]
+        elif self.type not in ["http", "https", "ftp", "ftps", "sftp"]:
+            self.md5_expected = None
+        else:
+            self.md5_expected = d.getVarFlag("SRC_URI", self.md5_name)
+        if self.sha256_name in self.parm:
+            self.sha256_expected = self.parm[self.sha256_name]
+        elif self.type not in ["http", "https", "ftp", "ftps", "sftp"]:
+            self.sha256_expected = None
+        else:
+            self.sha256_expected = d.getVarFlag("SRC_URI", self.sha256_name)
+        self.ignore_checksums = False
+
+        self.names = self.parm.get("name",'default').split(',')
+
+        self.method = None
+        for m in methods:
+            if m.supports(self, d):
+                self.method = m
+                break                
+
+        if not self.method:
+            raise NoMethodError(url)
+
+        if localonly and not isinstance(self.method, local.Local):
+            raise NonLocalMethod()
+
+        if self.parm.get("proto", None) and "protocol" not in self.parm:
+            logger.warn('Consider updating %s recipe to use "protocol" not "proto" in SRC_URI.', d.getVar('PN', True))
+            self.parm["protocol"] = self.parm.get("proto", None)
+
+        if hasattr(self.method, "urldata_init"):
+            self.method.urldata_init(self, d)
+
+        if "localpath" in self.parm:
+            # if user sets localpath for file, use it instead.
+            self.localpath = self.parm["localpath"]
+            self.basename = os.path.basename(self.localpath)
+        elif self.localfile:
+            self.localpath = self.method.localpath(self, d)
+
+        dldir = d.getVar("DL_DIR", True)
+        # Note: .done and .lock files should always be in DL_DIR whereas localpath may not be.
+        if self.localpath and self.localpath.startswith(dldir):
+            basepath = self.localpath
+        elif self.localpath:
+            basepath = dldir + os.sep + os.path.basename(self.localpath)
+        else:
+            basepath = dldir + os.sep + (self.basepath or self.basename)
+        self.donestamp = basepath + '.done'
+        self.lockfile = basepath + '.lock'
+
+    def setup_revisons(self, d):
+        self.revisions = {}
+        for name in self.names:
+            self.revisions[name] = srcrev_internal_helper(self, d, name)
+
+        # add compatibility code for non name specified case
+        if len(self.names) == 1:
+            self.revision = self.revisions[self.names[0]]
+
+    def setup_localpath(self, d):
+        if not self.localpath:
+            self.localpath = self.method.localpath(self, d)
+
+    def getSRCDate(self, d):
+        """
+        Return the SRC Date for the component
+
+        d the bb.data module
+        """
+        if "srcdate" in self.parm:
+            return self.parm['srcdate']
+
+        pn = d.getVar("PN", True)
+
+        if pn:
+            return d.getVar("SRCDATE_%s" % pn, True) or d.getVar("SRCDATE", True) or d.getVar("DATE", True)
+
+        return d.getVar("SRCDATE", True) or d.getVar("DATE", True)
+
+class FetchMethod(object):
+    """Base class for 'fetch'ing data"""
+
+    def __init__(self, urls=None):
+        self.urls = []
+
+    def supports(self, urldata, d):
+        """
+        Check to see if this fetch class supports a given url.
+        """
+        return 0
+
+    def localpath(self, urldata, d):
+        """
+        Return the local filename of a given url assuming a successful fetch.
+        Can also setup variables in urldata for use in go (saving code duplication
+        and duplicate code execution)
+        """
+        return os.path.join(data.getVar("DL_DIR", d, True), urldata.localfile)
+
+    def supports_checksum(self, urldata):
+        """
+        Is localpath something that can be represented by a checksum?
+        """
+
+        # We cannot compute checksums for directories
+        if os.path.isdir(urldata.localpath) == True:
+            return False
+        if urldata.localpath.find("*") != -1:
+             return False
+
+        return True
+
+    def recommends_checksum(self, urldata):
+        """
+        Is the backend on where checksumming is recommended (should warnings 
+        be displayed if there is no checksum)?
+        """
+        return False
+
+    def _strip_leading_slashes(self, relpath):
+        """
+        Remove leading slash as os.path.join can't cope
+        """
+        while os.path.isabs(relpath):
+            relpath = relpath[1:]
+        return relpath
+
+    def setUrls(self, urls):
+        self.__urls = urls
+
+    def getUrls(self):
+        return self.__urls
+
+    urls = property(getUrls, setUrls, None, "Urls property")
+
+    def need_update(self, ud, d):
+        """
+        Force a fetch, even if localpath exists?
+        """
+        if os.path.exists(ud.localpath):
+            return False
+        return True
+
+    def supports_srcrev(self):
+        """
+        The fetcher supports auto source revisions (SRCREV)
+        """
+        return False
+
+    def download(self, urldata, d):
+        """
+        Fetch urls
+        Assumes localpath was called first
+        """
+        raise NoMethodError(url)
+
+    def unpack(self, urldata, rootdir, data):
+        iterate = False
+        file = urldata.localpath
+
+        try:
+            unpack = bb.utils.to_boolean(urldata.parm.get('unpack'), True)
+        except ValueError as exc:
+            bb.fatal("Invalid value for 'unpack' parameter for %s: %s" %
+                     (file, urldata.parm.get('unpack')))
+
+        base, ext = os.path.splitext(file)
+        if ext in ['.gz', '.bz2', '.Z', '.xz', '.lz']:
+            efile = os.path.join(rootdir, os.path.basename(base))
+        else:
+            efile = file
+        cmd = None
+
+        if unpack:
+            if file.endswith('.tar'):
+                cmd = 'tar x --no-same-owner -f %s' % file
+            elif file.endswith('.tgz') or file.endswith('.tar.gz') or file.endswith('.tar.Z'):
+                cmd = 'tar xz --no-same-owner -f %s' % file
+            elif file.endswith('.tbz') or file.endswith('.tbz2') or file.endswith('.tar.bz2'):
+                cmd = 'bzip2 -dc %s | tar x --no-same-owner -f -' % file
+            elif file.endswith('.gz') or file.endswith('.Z') or file.endswith('.z'):
+                cmd = 'gzip -dc %s > %s' % (file, efile)
+            elif file.endswith('.bz2'):
+                cmd = 'bzip2 -dc %s > %s' % (file, efile)
+            elif file.endswith('.tar.xz'):
+                cmd = 'xz -dc %s | tar x --no-same-owner -f -' % file
+            elif file.endswith('.xz'):
+                cmd = 'xz -dc %s > %s' % (file, efile)
+            elif file.endswith('.tar.lz'):
+                cmd = 'lzip -dc %s | tar x --no-same-owner -f -' % file
+            elif file.endswith('.lz'):
+                cmd = 'lzip -dc %s > %s' % (file, efile)
+            elif file.endswith('.zip') or file.endswith('.jar'):
+                try:
+                    dos = bb.utils.to_boolean(urldata.parm.get('dos'), False)
+                except ValueError as exc:
+                    bb.fatal("Invalid value for 'dos' parameter for %s: %s" %
+                             (file, urldata.parm.get('dos')))
+                cmd = 'unzip -q -o'
+                if dos:
+                    cmd = '%s -a' % cmd
+                cmd = "%s '%s'" % (cmd, file)
+            elif file.endswith('.rpm') or file.endswith('.srpm'):
+                if 'extract' in urldata.parm:
+                    unpack_file = urldata.parm.get('extract')
+                    cmd = 'rpm2cpio.sh %s | cpio -id %s' % (file, unpack_file)
+                    iterate = True
+                    iterate_file = unpack_file
+                else:
+                    cmd = 'rpm2cpio.sh %s | cpio -id' % (file)
+            elif file.endswith('.deb') or file.endswith('.ipk'):
+                cmd = 'ar -p %s data.tar.gz | zcat | tar --no-same-owner -xpf -' % file
+
+        if not unpack or not cmd:
+            # If file == dest, then avoid any copies, as we already put the file into dest!
+            dest = os.path.join(rootdir, os.path.basename(file))
+            if (file != dest) and not (os.path.exists(dest) and os.path.samefile(file, dest)):
+                if os.path.isdir(file):
+                    # If for example we're asked to copy file://foo/bar, we need to unpack the result into foo/bar
+                    basepath = getattr(urldata, "basepath", None)
+                    destdir = "."
+                    if basepath and basepath.endswith("/"):
+                        basepath = basepath.rstrip("/")
+                    elif basepath:
+                        basepath = os.path.dirname(basepath)
+                    if basepath and basepath.find("/") != -1:
+                        destdir = basepath[:basepath.rfind('/')]
+                        destdir = destdir.strip('/')
+                    if destdir != "." and not os.access("%s/%s" % (rootdir, destdir), os.F_OK):
+                        os.makedirs("%s/%s" % (rootdir, destdir))
+                    cmd = 'cp -fpPR %s %s/%s/' % (file, rootdir, destdir)
+                    #cmd = 'tar -cf - -C "%d" -ps . | tar -xf - -C "%s/%s/"' % (file, rootdir, destdir)
+                else:
+                    # The "destdir" handling was specifically done for FILESPATH
+                    # items.  So, only do so for file:// entries.
+                    if urldata.type == "file" and urldata.path.find("/") != -1:
+                       destdir = urldata.path.rsplit("/", 1)[0]
+                       if urldata.parm.get('subdir') != None:
+                          destdir = urldata.parm.get('subdir') + "/" + destdir
+                    else:
+                       if urldata.parm.get('subdir') != None:
+                          destdir = urldata.parm.get('subdir')
+                       else:
+                          destdir = "."
+                    bb.utils.mkdirhier("%s/%s" % (rootdir, destdir))
+                    cmd = 'cp -f %s %s/%s/' % (file, rootdir, destdir)
+
+        if not cmd:
+            return
+
+        # Change to subdir before executing command
+        save_cwd = os.getcwd();
+        os.chdir(rootdir)
+        if 'subdir' in urldata.parm:
+            newdir = ("%s/%s" % (rootdir, urldata.parm.get('subdir')))
+            bb.utils.mkdirhier(newdir)
+            os.chdir(newdir)
+
+        path = data.getVar('PATH', True)
+        if path:
+            cmd = "PATH=\"%s\" %s" % (path, cmd)
+        bb.note("Unpacking %s to %s/" % (file, os.getcwd()))
+        ret = subprocess.call(cmd, preexec_fn=subprocess_setup, shell=True)
+
+        os.chdir(save_cwd)
+
+        if ret != 0:
+            raise UnpackError("Unpack command %s failed with return value %s" % (cmd, ret), urldata.url)
+
+        if iterate is True:
+            iterate_urldata = urldata
+            iterate_urldata.localpath = "%s/%s" % (rootdir, iterate_file)
+            self.unpack(urldata, rootdir, data)
+
+        return
+
+    def clean(self, urldata, d):
+        """
+        Clean any existing full or partial download
+        """
+        bb.utils.remove(urldata.localpath)
+
+    def try_premirror(self, urldata, d):
+        """
+        Should premirrors be used?
+        """
+        return True
+
+    def checkstatus(self, fetch, urldata, d):
+        """
+        Check the status of a URL
+        Assumes localpath was called first
+        """
+        logger.info("URL %s could not be checked for status since no method exists.", url)
+        return True
+
+    def latest_revision(self, ud, d, name):
+        """
+        Look in the cache for the latest revision, if not present ask the SCM.
+        """
+        if not hasattr(self, "_latest_revision"):
+            raise ParameterError("The fetcher for this URL does not support _latest_revision", url)
+
+        revs = bb.persist_data.persist('BB_URI_HEADREVS', d)
+        key = self.generate_revision_key(ud, d, name)
+        try:
+            return revs[key]
+        except KeyError:
+            revs[key] = rev = self._latest_revision(ud, d, name)
+            return rev
+
+    def sortable_revision(self, ud, d, name):
+        latest_rev = self._build_revision(ud, d, name)
+        return True, str(latest_rev)
+
+    def generate_revision_key(self, ud, d, name):
+        key = self._revision_key(ud, d, name)
+        return "%s-%s" % (key, d.getVar("PN", True) or "")
+
+class Fetch(object):
+    def __init__(self, urls, d, cache = True, localonly = False, connection_cache = None):
+        if localonly and cache:
+            raise Exception("bb.fetch2.Fetch.__init__: cannot set cache and localonly at same time")
+
+        if len(urls) == 0:
+            urls = d.getVar("SRC_URI", True).split()
+        self.urls = urls
+        self.d = d
+        self.ud = {}
+        self.connection_cache = connection_cache
+
+        fn = d.getVar('FILE', True)
+        if cache and fn and fn in urldata_cache:
+            self.ud = urldata_cache[fn]
+
+        for url in urls:
+            if url not in self.ud:
+                try:
+                    self.ud[url] = FetchData(url, d, localonly)
+                except NonLocalMethod:
+                    if localonly:
+                        self.ud[url] = None
+                        pass
+
+        if fn and cache:
+            urldata_cache[fn] = self.ud
+
+    def localpath(self, url):
+        if url not in self.urls:
+            self.ud[url] = FetchData(url, self.d)
+
+        self.ud[url].setup_localpath(self.d)
+        return self.d.expand(self.ud[url].localpath)
+
+    def localpaths(self):
+        """
+        Return a list of the local filenames, assuming successful fetch
+        """
+        local = []
+
+        for u in self.urls:
+            ud = self.ud[u]
+            ud.setup_localpath(self.d)
+            local.append(ud.localpath)
+
+        return local
+
+    def download(self, urls=None):
+        """
+        Fetch all urls
+        """
+        if not urls:
+            urls = self.urls
+
+        network = self.d.getVar("BB_NO_NETWORK", True)
+        premirroronly = (self.d.getVar("BB_FETCH_PREMIRRORONLY", True) == "1")
+
+        for u in urls:
+            ud = self.ud[u]
+            ud.setup_localpath(self.d)
+            m = ud.method
+            localpath = ""
+
+            lf = bb.utils.lockfile(ud.lockfile)
+
+            try:
+                self.d.setVar("BB_NO_NETWORK", network)
+ 
+                if verify_donestamp(ud, self.d) and not m.need_update(ud, self.d):
+                    localpath = ud.localpath
+                elif m.try_premirror(ud, self.d):
+                    logger.debug(1, "Trying PREMIRRORS")
+                    mirrors = mirror_from_string(self.d.getVar('PREMIRRORS', True))
+                    localpath = try_mirrors(self, self.d, ud, mirrors, False)
+
+                if premirroronly:
+                    self.d.setVar("BB_NO_NETWORK", "1")
+
+                os.chdir(self.d.getVar("DL_DIR", True))
+
+                firsterr = None
+                verified_stamp = verify_donestamp(ud, self.d)
+                if not localpath and (not verified_stamp or m.need_update(ud, self.d)):
+                    try:
+                        if not trusted_network(self.d, ud.url):
+                            raise UntrustedUrl(ud.url)
+                        logger.debug(1, "Trying Upstream")
+                        m.download(ud, self.d)
+                        if hasattr(m, "build_mirror_data"):
+                            m.build_mirror_data(ud, self.d)
+                        localpath = ud.localpath
+                        # early checksum verify, so that if checksum mismatched,
+                        # fetcher still have chance to fetch from mirror
+                        update_stamp(ud, self.d)
+
+                    except bb.fetch2.NetworkAccess:
+                        raise
+
+                    except BBFetchException as e:
+                        if isinstance(e, ChecksumError):
+                            logger.warn("Checksum failure encountered with download of %s - will attempt other sources if available" % u)
+                            logger.debug(1, str(e))
+                            rename_bad_checksum(ud, e.checksum)
+                        elif isinstance(e, NoChecksumError):
+                            raise
+                        else:
+                            logger.warn('Failed to fetch URL %s, attempting MIRRORS if available' % u)
+                            logger.debug(1, str(e))
+                        firsterr = e
+                        # Remove any incomplete fetch
+                        if not verified_stamp:
+                            m.clean(ud, self.d)
+                        logger.debug(1, "Trying MIRRORS")
+                        mirrors = mirror_from_string(self.d.getVar('MIRRORS', True))
+                        localpath = try_mirrors(self, self.d, ud, mirrors)
+
+                if not localpath or ((not os.path.exists(localpath)) and localpath.find("*") == -1):
+                    if firsterr:
+                        logger.error(str(firsterr))
+                    raise FetchError("Unable to fetch URL from any source.", u)
+
+                update_stamp(ud, self.d)
+
+            except BBFetchException as e:
+                if isinstance(e, ChecksumError):
+                    logger.error("Checksum failure fetching %s" % u)
+                raise
+
+            finally:
+                bb.utils.unlockfile(lf)
+
+    def checkstatus(self, urls=None):
+        """
+        Check all urls exist upstream
+        """
+
+        if not urls:
+            urls = self.urls
+
+        for u in urls:
+            ud = self.ud[u]
+            ud.setup_localpath(self.d)
+            m = ud.method
+            logger.debug(1, "Testing URL %s", u)
+            # First try checking uri, u, from PREMIRRORS
+            mirrors = mirror_from_string(self.d.getVar('PREMIRRORS', True))
+            ret = try_mirrors(self, self.d, ud, mirrors, True)
+            if not ret:
+                # Next try checking from the original uri, u
+                try:
+                    ret = m.checkstatus(self, ud, self.d)
+                except:
+                    # Finally, try checking uri, u, from MIRRORS
+                    mirrors = mirror_from_string(self.d.getVar('MIRRORS', True))
+                    ret = try_mirrors(self, self.d, ud, mirrors, True)
+
+            if not ret:
+                raise FetchError("URL %s doesn't work" % u, u)
+
+    def unpack(self, root, urls=None):
+        """
+        Check all urls exist upstream
+        """
+
+        if not urls:
+            urls = self.urls
+
+        for u in urls:
+            ud = self.ud[u]
+            ud.setup_localpath(self.d)
+
+            if self.d.expand(self.localpath) is None:
+                continue
+
+            if ud.lockfile:
+                lf = bb.utils.lockfile(ud.lockfile)
+
+            ud.method.unpack(ud, root, self.d)
+
+            if ud.lockfile:
+                bb.utils.unlockfile(lf)
+
+    def clean(self, urls=None):
+        """
+        Clean files that the fetcher gets or places
+        """
+
+        if not urls:
+            urls = self.urls
+
+        for url in urls:
+            if url not in self.ud:
+                self.ud[url] = FetchData(url, d)
+            ud = self.ud[url]
+            ud.setup_localpath(self.d)
+
+            if not ud.localfile and ud.localpath is None:
+                continue
+
+            if ud.lockfile:
+                lf = bb.utils.lockfile(ud.lockfile)
+
+            ud.method.clean(ud, self.d)
+            if ud.donestamp:
+                bb.utils.remove(ud.donestamp)
+
+            if ud.lockfile:
+                bb.utils.unlockfile(lf)
+
+class FetchConnectionCache(object):
+    """
+        A class which represents an container for socket connections.
+    """
+    def __init__(self):
+        self.cache = {}
+
+    def get_connection_name(self, host, port):
+        return host + ':' + str(port)
+
+    def add_connection(self, host, port, connection):
+        cn = self.get_connection_name(host, port)
+
+        if cn not in self.cache:
+            self.cache[cn] = connection
+
+    def get_connection(self, host, port):
+        connection = None
+
+        cn = self.get_connection_name(host, port)
+        if cn in self.cache:
+            connection = self.cache[cn]
+
+        return connection
+
+    def remove_connection(self, host, port):
+        cn = self.get_connection_name(host, port)
+        if cn in self.cache:
+            self.cache[cn].close()
+            del self.cache[cn]
+
+    def close_connections(self):
+        for cn in self.cache.keys():
+            self.cache[cn].close()
+            del self.cache[cn]
+
+from . import cvs
+from . import git
+from . import gitsm
+from . import gitannex
+from . import local
+from . import svn
+from . import wget
+from . import ssh
+from . import sftp
+from . import perforce
+from . import bzr
+from . import hg
+from . import osc
+from . import repo
+from . import clearcase
+
+methods.append(local.Local())
+methods.append(wget.Wget())
+methods.append(svn.Svn())
+methods.append(git.Git())
+methods.append(gitsm.GitSM())
+methods.append(gitannex.GitANNEX())
+methods.append(cvs.Cvs())
+methods.append(ssh.SSH())
+methods.append(sftp.SFTP())
+methods.append(perforce.Perforce())
+methods.append(bzr.Bzr())
+methods.append(hg.Hg())
+methods.append(osc.Osc())
+methods.append(repo.Repo())
+methods.append(clearcase.ClearCase())
diff --git a/bitbake/lib/bb/fetch2/bzr.py b/bitbake/lib/bb/fetch2/bzr.py
new file mode 100644
index 0000000..03e9ac4
--- /dev/null
+++ b/bitbake/lib/bb/fetch2/bzr.py
@@ -0,0 +1,143 @@
+"""
+BitBake 'Fetch' implementation for bzr.
+
+"""
+
+# Copyright (C) 2007 Ross Burton
+# Copyright (C) 2007 Richard Purdie
+#
+#   Classes for obtaining upstream sources for the
+#   BitBake build tools.
+#   Copyright (C) 2003, 2004  Chris Larson
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import os
+import sys
+import logging
+import bb
+from bb import data
+from bb.fetch2 import FetchMethod
+from bb.fetch2 import FetchError
+from bb.fetch2 import runfetchcmd
+from bb.fetch2 import logger
+
+class Bzr(FetchMethod):
+    def supports(self, ud, d):
+        return ud.type in ['bzr']
+
+    def urldata_init(self, ud, d):
+        """
+        init bzr specific variable within url data
+        """
+        # Create paths to bzr checkouts
+        relpath = self._strip_leading_slashes(ud.path)
+        ud.pkgdir = os.path.join(data.expand('${BZRDIR}', d), ud.host, relpath)
+
+        ud.setup_revisons(d)
+
+        if not ud.revision:
+            ud.revision = self.latest_revision(ud, d)
+
+        ud.localfile = data.expand('bzr_%s_%s_%s.tar.gz' % (ud.host, ud.path.replace('/', '.'), ud.revision), d)
+
+    def _buildbzrcommand(self, ud, d, command):
+        """
+        Build up an bzr commandline based on ud
+        command is "fetch", "update", "revno"
+        """
+
+        basecmd = data.expand('${FETCHCMD_bzr}', d)
+
+        proto =  ud.parm.get('protocol', 'http')
+
+        bzrroot = ud.host + ud.path
+
+        options = []
+
+        if command == "revno":
+            bzrcmd = "%s revno %s %s://%s" % (basecmd, " ".join(options), proto, bzrroot)
+        else:
+            if ud.revision:
+                options.append("-r %s" % ud.revision)
+
+            if command == "fetch":
+                bzrcmd = "%s branch %s %s://%s" % (basecmd, " ".join(options), proto, bzrroot)
+            elif command == "update":
+                bzrcmd = "%s pull %s --overwrite" % (basecmd, " ".join(options))
+            else:
+                raise FetchError("Invalid bzr command %s" % command, ud.url)
+
+        return bzrcmd
+
+    def download(self, ud, d):
+        """Fetch url"""
+
+        if os.access(os.path.join(ud.pkgdir, os.path.basename(ud.pkgdir), '.bzr'), os.R_OK):
+            bzrcmd = self._buildbzrcommand(ud, d, "update")
+            logger.debug(1, "BZR Update %s", ud.url)
+            bb.fetch2.check_network_access(d, bzrcmd, ud.url)
+            os.chdir(os.path.join (ud.pkgdir, os.path.basename(ud.path)))
+            runfetchcmd(bzrcmd, d)
+        else:
+            bb.utils.remove(os.path.join(ud.pkgdir, os.path.basename(ud.pkgdir)), True)
+            bzrcmd = self._buildbzrcommand(ud, d, "fetch")
+            bb.fetch2.check_network_access(d, bzrcmd, ud.url)
+            logger.debug(1, "BZR Checkout %s", ud.url)
+            bb.utils.mkdirhier(ud.pkgdir)
+            os.chdir(ud.pkgdir)
+            logger.debug(1, "Running %s", bzrcmd)
+            runfetchcmd(bzrcmd, d)
+
+        os.chdir(ud.pkgdir)
+
+        scmdata = ud.parm.get("scmdata", "")
+        if scmdata == "keep":
+            tar_flags = ""
+        else:
+            tar_flags = "--exclude '.bzr' --exclude '.bzrtags'"
+
+        # tar them up to a defined filename
+        runfetchcmd("tar %s -czf %s %s" % (tar_flags, ud.localpath, os.path.basename(ud.pkgdir)), d, cleanup = [ud.localpath])
+
+    def supports_srcrev(self):
+        return True
+
+    def _revision_key(self, ud, d, name):
+        """
+        Return a unique key for the url
+        """
+        return "bzr:" + ud.pkgdir
+
+    def _latest_revision(self, ud, d, name):
+        """
+        Return the latest upstream revision number
+        """
+        logger.debug(2, "BZR fetcher hitting network for %s", ud.url)
+
+        bb.fetch2.check_network_access(d, self._buildbzrcommand(ud, d, "revno"), ud.url)
+
+        output = runfetchcmd(self._buildbzrcommand(ud, d, "revno"), d, True)
+
+        return output.strip()
+
+    def sortable_revision(self, ud, d, name):
+        """
+        Return a sortable revision number which in our case is the revision number
+        """
+
+        return False, self._build_revision(ud, d)
+
+    def _build_revision(self, ud, d):
+        return ud.revision
diff --git a/bitbake/lib/bb/fetch2/clearcase.py b/bitbake/lib/bb/fetch2/clearcase.py
new file mode 100644
index 0000000..ba83e7c
--- /dev/null
+++ b/bitbake/lib/bb/fetch2/clearcase.py
@@ -0,0 +1,263 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake 'Fetch' clearcase implementation
+
+The clearcase fetcher is used to retrieve files from a ClearCase repository.
+
+Usage in the recipe:
+
+    SRC_URI = "ccrc://cc.example.org/ccrc;vob=/example_vob;module=/example_module"
+    SRCREV = "EXAMPLE_CLEARCASE_TAG"
+    PV = "${@d.getVar("SRCREV", False).replace("/", "+")}"
+
+The fetcher uses the rcleartool or cleartool remote client, depending on which one is available.
+
+Supported SRC_URI options are:
+
+- vob
+    (required) The name of the clearcase VOB (with prepending "/")
+
+- module
+    The module in the selected VOB (with prepending "/")
+
+    The module and vob parameters are combined to create
+    the following load rule in the view config spec:
+                load <vob><module>
+
+- proto
+    http or https
+
+Related variables:
+
+    CCASE_CUSTOM_CONFIG_SPEC
+            Write a config spec to this variable in your recipe to use it instead
+            of the default config spec generated by this fetcher.
+            Please note that the SRCREV loses its functionality if you specify
+            this variable. SRCREV is still used to label the archive after a fetch,
+            but it doesn't define what's fetched.
+
+User credentials:
+    cleartool:
+            The login of cleartool is handled by the system. No special steps needed.
+
+    rcleartool:
+            In order to use rcleartool with authenticated users an `rcleartool login` is
+            necessary before using the fetcher.
+"""
+# Copyright (C) 2014 Siemens AG
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+import os
+import sys
+import shutil
+import bb
+from   bb import data
+from   bb.fetch2 import FetchMethod
+from   bb.fetch2 import FetchError
+from   bb.fetch2 import runfetchcmd
+from   bb.fetch2 import logger
+from   distutils import spawn
+
+class ClearCase(FetchMethod):
+    """Class to fetch urls via 'clearcase'"""
+    def init(self, d):
+        pass
+
+    def supports(self, ud, d):
+        """
+        Check to see if a given url can be fetched with Clearcase.
+        """
+        return ud.type in ['ccrc']
+
+    def debug(self, msg):
+        logger.debug(1, "ClearCase: %s", msg)
+
+    def urldata_init(self, ud, d):
+        """
+        init ClearCase specific variable within url data
+        """
+        ud.proto = "https"
+        if 'protocol' in ud.parm:
+            ud.proto = ud.parm['protocol']
+        if not ud.proto in ('http', 'https'):
+            raise fetch2.ParameterError("Invalid protocol type", ud.url)
+
+        ud.vob = ''
+        if 'vob' in ud.parm:
+            ud.vob = ud.parm['vob']
+        else:
+            msg = ud.url+": vob must be defined so the fetcher knows what to get."
+            raise MissingParameterError('vob', msg)
+
+        if 'module' in ud.parm:
+            ud.module = ud.parm['module']
+        else:
+            ud.module = ""
+
+        ud.basecmd = d.getVar("FETCHCMD_ccrc", True) or spawn.find_executable("cleartool") or spawn.find_executable("rcleartool")
+
+        if data.getVar("SRCREV", d, True) == "INVALID":
+          raise FetchError("Set a valid SRCREV for the clearcase fetcher in your recipe, e.g. SRCREV = \"/main/LATEST\" or any other label of your choice.")
+
+        ud.label = d.getVar("SRCREV", False)
+        ud.customspec = d.getVar("CCASE_CUSTOM_CONFIG_SPEC", True)
+
+        ud.server     = "%s://%s%s" % (ud.proto, ud.host, ud.path)
+
+        ud.identifier = "clearcase-%s%s-%s" % ( ud.vob.replace("/", ""),
+                                                ud.module.replace("/", "."),
+                                                ud.label.replace("/", "."))
+
+        ud.viewname         = "%s-view%s" % (ud.identifier, d.getVar("DATETIME", d, True))
+        ud.csname           = "%s-config-spec" % (ud.identifier)
+        ud.ccasedir         = os.path.join(data.getVar("DL_DIR", d, True), ud.type)
+        ud.viewdir          = os.path.join(ud.ccasedir, ud.viewname)
+        ud.configspecfile   = os.path.join(ud.ccasedir, ud.csname)
+        ud.localfile        = "%s.tar.gz" % (ud.identifier)
+
+        self.debug("host            = %s" % ud.host)
+        self.debug("path            = %s" % ud.path)
+        self.debug("server          = %s" % ud.server)
+        self.debug("proto           = %s" % ud.proto)
+        self.debug("type            = %s" % ud.type)
+        self.debug("vob             = %s" % ud.vob)
+        self.debug("module          = %s" % ud.module)
+        self.debug("basecmd         = %s" % ud.basecmd)
+        self.debug("label           = %s" % ud.label)
+        self.debug("ccasedir        = %s" % ud.ccasedir)
+        self.debug("viewdir         = %s" % ud.viewdir)
+        self.debug("viewname        = %s" % ud.viewname)
+        self.debug("configspecfile  = %s" % ud.configspecfile)
+        self.debug("localfile       = %s" % ud.localfile)
+
+        ud.localfile = os.path.join(data.getVar("DL_DIR", d, True), ud.localfile)
+
+    def _build_ccase_command(self, ud, command):
+        """
+        Build up a commandline based on ud
+        command is: mkview, setcs, rmview
+        """
+        options = []
+
+        if "rcleartool" in ud.basecmd:
+            options.append("-server %s" % ud.server)
+
+        basecmd = "%s %s" % (ud.basecmd, command)
+
+        if command is 'mkview':
+            if not "rcleartool" in ud.basecmd:
+                # Cleartool needs a -snapshot view
+                options.append("-snapshot")
+            options.append("-tag %s" % ud.viewname)
+            options.append(ud.viewdir)
+
+        elif command is 'rmview':
+            options.append("-force")
+            options.append("%s" % ud.viewdir)
+
+        elif command is 'setcs':
+            options.append("-overwrite")
+            options.append(ud.configspecfile)
+
+        else:
+            raise FetchError("Invalid ccase command %s" % command)
+
+        ccasecmd = "%s %s" % (basecmd, " ".join(options))
+        self.debug("ccasecmd = %s" % ccasecmd)
+        return ccasecmd
+
+    def _write_configspec(self, ud, d):
+        """
+        Create config spec file (ud.configspecfile) for ccase view
+        """
+        config_spec = ""
+        custom_config_spec = d.getVar("CCASE_CUSTOM_CONFIG_SPEC", d)
+        if custom_config_spec is not None:
+            for line in custom_config_spec.split("\\n"):
+                config_spec += line+"\n"
+            bb.warn("A custom config spec has been set, SRCREV is only relevant for the tarball name.")
+        else:
+            config_spec += "element * CHECKEDOUT\n"
+            config_spec += "element * %s\n" % ud.label
+            config_spec += "load %s%s\n" % (ud.vob, ud.module)
+
+        logger.info("Using config spec: \n%s" % config_spec)
+
+        with open(ud.configspecfile, 'w') as f:
+            f.write(config_spec)
+
+    def _remove_view(self, ud, d):
+        if os.path.exists(ud.viewdir):
+            os.chdir(ud.ccasedir)
+            cmd = self._build_ccase_command(ud, 'rmview');
+            logger.info("cleaning up [VOB=%s label=%s view=%s]", ud.vob, ud.label, ud.viewname)
+            bb.fetch2.check_network_access(d, cmd, ud.url)
+            output = runfetchcmd(cmd, d)
+            logger.info("rmview output: %s", output)
+
+    def need_update(self, ud, d):
+        if ("LATEST" in ud.label) or (ud.customspec and "LATEST" in ud.customspec):
+            ud.identifier += "-%s" % d.getVar("DATETIME",d, True)
+            return True
+        if os.path.exists(ud.localpath):
+            return False
+        return True
+
+    def supports_srcrev(self):
+        return True
+
+    def sortable_revision(self, ud, d, name):
+        return False, ud.identifier
+
+    def download(self, ud, d):
+        """Fetch url"""
+
+        # Make a fresh view
+        bb.utils.mkdirhier(ud.ccasedir)
+        self._write_configspec(ud, d)
+        cmd = self._build_ccase_command(ud, 'mkview')
+        logger.info("creating view [VOB=%s label=%s view=%s]", ud.vob, ud.label, ud.viewname)
+        bb.fetch2.check_network_access(d, cmd, ud.url)
+        try:
+            runfetchcmd(cmd, d)
+        except FetchError as e:
+            if "CRCLI2008E" in e.msg:
+                raise FetchError("%s\n%s\n" % (e.msg, "Call `rcleartool login` in your console to authenticate to the clearcase server before running bitbake."))
+            else:
+                raise e
+
+        # Set configspec: Setting the configspec effectively fetches the files as defined in the configspec
+        os.chdir(ud.viewdir)
+        cmd = self._build_ccase_command(ud, 'setcs');
+        logger.info("fetching data [VOB=%s label=%s view=%s]", ud.vob, ud.label, ud.viewname)
+        bb.fetch2.check_network_access(d, cmd, ud.url)
+        output = runfetchcmd(cmd, d)
+        logger.info("%s", output)
+
+        # Copy the configspec to the viewdir so we have it in our source tarball later
+        shutil.copyfile(ud.configspecfile, os.path.join(ud.viewdir, ud.csname))
+
+        # Clean clearcase meta-data before tar
+
+        runfetchcmd('tar -czf "%s" .' % (ud.localpath), d, cleanup = [ud.localpath])
+
+        # Clean up so we can create a new view next time
+        self.clean(ud, d);
+
+    def clean(self, ud, d):
+        self._remove_view(ud, d)
+        bb.utils.remove(ud.configspecfile)
diff --git a/bitbake/lib/bb/fetch2/cvs.py b/bitbake/lib/bb/fetch2/cvs.py
new file mode 100644
index 0000000..d27d96f
--- /dev/null
+++ b/bitbake/lib/bb/fetch2/cvs.py
@@ -0,0 +1,171 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake 'Fetch' implementations
+
+Classes for obtaining upstream sources for the
+BitBake build tools.
+
+"""
+
+# Copyright (C) 2003, 2004  Chris Larson
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#Based on functions from the base bb module, Copyright 2003 Holger Schurig
+#
+
+import os
+import logging
+import bb
+from bb.fetch2 import FetchMethod, FetchError, MissingParameterError, logger
+from bb.fetch2 import runfetchcmd
+
+class Cvs(FetchMethod):
+    """
+    Class to fetch a module or modules from cvs repositories
+    """
+    def supports(self, ud, d):
+        """
+        Check to see if a given url can be fetched with cvs.
+        """
+        return ud.type in ['cvs']
+
+    def urldata_init(self, ud, d):
+        if not "module" in ud.parm:
+            raise MissingParameterError("module", ud.url)
+        ud.module = ud.parm["module"]
+
+        ud.tag = ud.parm.get('tag', "")
+
+        # Override the default date in certain cases
+        if 'date' in ud.parm:
+            ud.date = ud.parm['date']
+        elif ud.tag:
+            ud.date = ""
+
+        norecurse = ''
+        if 'norecurse' in ud.parm:
+            norecurse = '_norecurse'
+
+        fullpath = ''
+        if 'fullpath' in ud.parm:
+            fullpath = '_fullpath'
+
+        ud.localfile = bb.data.expand('%s_%s_%s_%s%s%s.tar.gz' % (ud.module.replace('/', '.'), ud.host, ud.tag, ud.date, norecurse, fullpath), d)
+
+    def need_update(self, ud, d):
+        if (ud.date == "now"):
+            return True
+        if not os.path.exists(ud.localpath):
+            return True
+        return False
+
+    def download(self, ud, d):
+
+        method = ud.parm.get('method', 'pserver')
+        localdir = ud.parm.get('localdir', ud.module)
+        cvs_port = ud.parm.get('port', '')
+
+        cvs_rsh = None
+        if method == "ext":
+            if "rsh" in ud.parm:
+                cvs_rsh = ud.parm["rsh"]
+
+        if method == "dir":
+            cvsroot = ud.path
+        else:
+            cvsroot = ":" + method
+            cvsproxyhost = d.getVar('CVS_PROXY_HOST', True)
+            if cvsproxyhost:
+                cvsroot += ";proxy=" + cvsproxyhost
+            cvsproxyport = d.getVar('CVS_PROXY_PORT', True)
+            if cvsproxyport:
+                cvsroot += ";proxyport=" + cvsproxyport
+            cvsroot += ":" + ud.user
+            if ud.pswd:
+                cvsroot += ":" + ud.pswd
+            cvsroot += "@" + ud.host + ":" + cvs_port + ud.path
+
+        options = []
+        if 'norecurse' in ud.parm:
+            options.append("-l")
+        if ud.date:
+            # treat YYYYMMDDHHMM specially for CVS
+            if len(ud.date) == 12:
+                options.append("-D \"%s %s:%s UTC\"" % (ud.date[0:8], ud.date[8:10], ud.date[10:12]))
+            else:
+                options.append("-D \"%s UTC\"" % ud.date)
+        if ud.tag:
+            options.append("-r %s" % ud.tag)
+
+        cvsbasecmd = d.getVar("FETCHCMD_cvs", True)
+        cvscmd = cvsbasecmd + " '-d" + cvsroot + "' co " + " ".join(options) + " " + ud.module
+        cvsupdatecmd = cvsbasecmd + " '-d" + cvsroot + "' update -d -P " + " ".join(options)
+
+        if cvs_rsh:
+            cvscmd = "CVS_RSH=\"%s\" %s" % (cvs_rsh, cvscmd)
+            cvsupdatecmd = "CVS_RSH=\"%s\" %s" % (cvs_rsh, cvsupdatecmd)
+
+        # create module directory
+        logger.debug(2, "Fetch: checking for module directory")
+        pkg = d.getVar('PN', True)
+        pkgdir = os.path.join(d.getVar('CVSDIR', True), pkg)
+        moddir = os.path.join(pkgdir, localdir)
+        if os.access(os.path.join(moddir, 'CVS'), os.R_OK):
+            logger.info("Update " + ud.url)
+            bb.fetch2.check_network_access(d, cvsupdatecmd, ud.url)
+            # update sources there
+            os.chdir(moddir)
+            cmd = cvsupdatecmd
+        else:
+            logger.info("Fetch " + ud.url)
+            # check out sources there
+            bb.utils.mkdirhier(pkgdir)
+            os.chdir(pkgdir)
+            logger.debug(1, "Running %s", cvscmd)
+            bb.fetch2.check_network_access(d, cvscmd, ud.url)
+            cmd = cvscmd
+
+        runfetchcmd(cmd, d, cleanup = [moddir])
+
+        if not os.access(moddir, os.R_OK):
+            raise FetchError("Directory %s was not readable despite sucessful fetch?!" % moddir, ud.url)
+
+        scmdata = ud.parm.get("scmdata", "")
+        if scmdata == "keep":
+            tar_flags = ""
+        else:
+            tar_flags = "--exclude 'CVS'"
+
+        # tar them up to a defined filename
+        if 'fullpath' in ud.parm:
+            os.chdir(pkgdir)
+            cmd = "tar %s -czf %s %s" % (tar_flags, ud.localpath, localdir)
+        else:
+            os.chdir(moddir)
+            os.chdir('..')
+            cmd = "tar %s -czf %s %s" % (tar_flags, ud.localpath, os.path.basename(moddir))
+
+        runfetchcmd(cmd, d, cleanup = [ud.localpath])
+
+    def clean(self, ud, d):
+        """ Clean CVS Files and tarballs """
+
+        pkg = d.getVar('PN', True)
+        pkgdir = os.path.join(d.getVar("CVSDIR", True), pkg)
+
+        bb.utils.remove(pkgdir, True)
+        bb.utils.remove(ud.localpath)
+
diff --git a/bitbake/lib/bb/fetch2/git.py b/bitbake/lib/bb/fetch2/git.py
new file mode 100644
index 0000000..40658ff
--- /dev/null
+++ b/bitbake/lib/bb/fetch2/git.py
@@ -0,0 +1,447 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake 'Fetch' git implementation
+
+git fetcher support the SRC_URI with format of:
+SRC_URI = "git://some.host/somepath;OptionA=xxx;OptionB=xxx;..."
+
+Supported SRC_URI options are:
+
+- branch
+   The git branch to retrieve from. The default is "master"
+
+   This option also supports multiple branch fetching, with branches
+   separated by commas.  In multiple branches case, the name option
+   must have the same number of names to match the branches, which is
+   used to specify the SRC_REV for the branch
+   e.g:
+   SRC_URI="git://some.host/somepath;branch=branchX,branchY;name=nameX,nameY"
+   SRCREV_nameX = "xxxxxxxxxxxxxxxxxxxx"
+   SRCREV_nameY = "YYYYYYYYYYYYYYYYYYYY"
+
+- tag
+    The git tag to retrieve. The default is "master"
+
+- protocol
+   The method to use to access the repository. Common options are "git",
+   "http", "https", "file", "ssh" and "rsync". The default is "git".
+
+- rebaseable
+   rebaseable indicates that the upstream git repo may rebase in the future,
+   and current revision may disappear from upstream repo. This option will
+   remind fetcher to preserve local cache carefully for future use.
+   The default value is "0", set rebaseable=1 for rebaseable git repo.
+
+- nocheckout
+   Don't checkout source code when unpacking. set this option for the recipe
+   who has its own routine to checkout code.
+   The default is "0", set nocheckout=1 if needed.
+
+- bareclone
+   Create a bare clone of the source code and don't checkout the source code
+   when unpacking. Set this option for the recipe who has its own routine to
+   checkout code and tracking branch requirements.
+   The default is "0", set bareclone=1 if needed.
+
+- nobranch
+   Don't check the SHA validation for branch. set this option for the recipe
+   referring to commit which is valid in tag instead of branch.
+   The default is "0", set nobranch=1 if needed.
+
+"""
+
+#Copyright (C) 2005 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import os
+import re
+import bb
+from   bb    import data
+from   bb.fetch2 import FetchMethod
+from   bb.fetch2 import runfetchcmd
+from   bb.fetch2 import logger
+
+class Git(FetchMethod):
+    """Class to fetch a module or modules from git repositories"""
+    def init(self, d):
+        pass
+
+    def supports(self, ud, d):
+        """
+        Check to see if a given url can be fetched with git.
+        """
+        return ud.type in ['git']
+
+    def supports_checksum(self, urldata):
+        return False
+
+    def urldata_init(self, ud, d):
+        """
+        init git specific variable within url data
+        so that the git method like latest_revision() can work
+        """
+        if 'protocol' in ud.parm:
+            ud.proto = ud.parm['protocol']
+        elif not ud.host:
+            ud.proto = 'file'
+        else:
+            ud.proto = "git"
+
+        if not ud.proto in ('git', 'file', 'ssh', 'http', 'https', 'rsync'):
+            raise bb.fetch2.ParameterError("Invalid protocol type", ud.url)
+
+        ud.nocheckout = ud.parm.get("nocheckout","0") == "1"
+
+        ud.rebaseable = ud.parm.get("rebaseable","0") == "1"
+
+        ud.nobranch = ud.parm.get("nobranch","0") == "1"
+
+        # bareclone implies nocheckout
+        ud.bareclone = ud.parm.get("bareclone","0") == "1"
+        if ud.bareclone:
+            ud.nocheckout = 1
+  
+        ud.unresolvedrev = {}
+        branches = ud.parm.get("branch", "master").split(',')
+        if len(branches) != len(ud.names):
+            raise bb.fetch2.ParameterError("The number of name and branch parameters is not balanced", ud.url)
+        ud.branches = {}
+        for name in ud.names:
+            branch = branches[ud.names.index(name)]
+            ud.branches[name] = branch
+            ud.unresolvedrev[name] = branch
+
+        ud.basecmd = data.getVar("FETCHCMD_git", d, True) or "git -c core.fsyncobjectfiles=0"
+
+        ud.write_tarballs = ((data.getVar("BB_GENERATE_MIRROR_TARBALLS", d, True) or "0") != "0") or ud.rebaseable
+
+        ud.setup_revisons(d)
+
+        for name in ud.names:
+            # Ensure anything that doesn't look like a sha256 checksum/revision is translated into one
+            if not ud.revisions[name] or len(ud.revisions[name]) != 40  or (False in [c in "abcdef0123456789" for c in ud.revisions[name]]):
+                if ud.revisions[name]:
+                    ud.unresolvedrev[name] = ud.revisions[name]
+                ud.revisions[name] = self.latest_revision(ud, d, name)
+
+        gitsrcname = '%s%s' % (ud.host.replace(':', '.'), ud.path.replace('/', '.').replace('*', '.'))
+        if gitsrcname.startswith('.'):
+            gitsrcname = gitsrcname[1:]
+
+        # for rebaseable git repo, it is necessary to keep mirror tar ball
+        # per revision, so that even the revision disappears from the
+        # upstream repo in the future, the mirror will remain intact and still
+        # contains the revision
+        if ud.rebaseable:
+            for name in ud.names:
+                gitsrcname = gitsrcname + '_' + ud.revisions[name]
+        ud.mirrortarball = 'git2_%s.tar.gz' % (gitsrcname)
+        ud.fullmirror = os.path.join(d.getVar("DL_DIR", True), ud.mirrortarball)
+        gitdir = d.getVar("GITDIR", True) or (d.getVar("DL_DIR", True) + "/git2/")
+        ud.clonedir = os.path.join(gitdir, gitsrcname)
+
+        ud.localfile = ud.clonedir
+
+    def localpath(self, ud, d):
+        return ud.clonedir
+
+    def need_update(self, ud, d):
+        if not os.path.exists(ud.clonedir):
+            return True
+        os.chdir(ud.clonedir)
+        for name in ud.names:
+            if not self._contains_ref(ud, d, name):
+                return True
+        if ud.write_tarballs and not os.path.exists(ud.fullmirror):
+            return True
+        return False
+
+    def try_premirror(self, ud, d):
+        # If we don't do this, updating an existing checkout with only premirrors
+        # is not possible
+        if d.getVar("BB_FETCH_PREMIRRORONLY", True) is not None:
+            return True
+        if os.path.exists(ud.clonedir):
+            return False
+        return True
+
+    def download(self, ud, d):
+        """Fetch url"""
+
+        ud.repochanged = not os.path.exists(ud.fullmirror)
+
+        # If the checkout doesn't exist and the mirror tarball does, extract it
+        if not os.path.exists(ud.clonedir) and os.path.exists(ud.fullmirror):
+            bb.utils.mkdirhier(ud.clonedir)
+            os.chdir(ud.clonedir)
+            runfetchcmd("tar -xzf %s" % (ud.fullmirror), d)
+
+        repourl = self._get_repo_url(ud)
+
+        # If the repo still doesn't exist, fallback to cloning it
+        if not os.path.exists(ud.clonedir):
+            # We do this since git will use a "-l" option automatically for local urls where possible
+            if repourl.startswith("file://"):
+                repourl = repourl[7:]
+            clone_cmd = "%s clone --bare --mirror %s %s" % (ud.basecmd, repourl, ud.clonedir)
+            if ud.proto.lower() != 'file':
+                bb.fetch2.check_network_access(d, clone_cmd)
+            runfetchcmd(clone_cmd, d)
+
+        os.chdir(ud.clonedir)
+        # Update the checkout if needed
+        needupdate = False
+        for name in ud.names:
+            if not self._contains_ref(ud, d, name):
+                needupdate = True
+        if needupdate:
+            try: 
+                runfetchcmd("%s remote rm origin" % ud.basecmd, d) 
+            except bb.fetch2.FetchError:
+                logger.debug(1, "No Origin")
+
+            runfetchcmd("%s remote add --mirror=fetch origin %s" % (ud.basecmd, repourl), d)
+            fetch_cmd = "%s fetch -f --prune %s refs/*:refs/*" % (ud.basecmd, repourl)
+            if ud.proto.lower() != 'file':
+                bb.fetch2.check_network_access(d, fetch_cmd, ud.url)
+            runfetchcmd(fetch_cmd, d)
+            runfetchcmd("%s prune-packed" % ud.basecmd, d)
+            runfetchcmd("%s pack-redundant --all | xargs -r rm" % ud.basecmd, d)
+            ud.repochanged = True
+        os.chdir(ud.clonedir)
+        for name in ud.names:
+            if not self._contains_ref(ud, d, name):
+                raise bb.fetch2.FetchError("Unable to find revision %s in branch %s even from upstream" % (ud.revisions[name], ud.branches[name]))
+
+    def build_mirror_data(self, ud, d):
+        # Generate a mirror tarball if needed
+        if ud.write_tarballs and (ud.repochanged or not os.path.exists(ud.fullmirror)):
+            # it's possible that this symlink points to read-only filesystem with PREMIRROR
+            if os.path.islink(ud.fullmirror):
+                os.unlink(ud.fullmirror)
+
+            os.chdir(ud.clonedir)
+            logger.info("Creating tarball of git repository")
+            runfetchcmd("tar -czf %s %s" % (ud.fullmirror, os.path.join(".") ), d)
+            runfetchcmd("touch %s.done" % (ud.fullmirror), d)
+
+    def unpack(self, ud, destdir, d):
+        """ unpack the downloaded src to destdir"""
+
+        subdir = ud.parm.get("subpath", "")
+        if subdir != "":
+            readpathspec = ":%s" % (subdir)
+            def_destsuffix = "%s/" % os.path.basename(subdir.rstrip('/'))
+        else:
+            readpathspec = ""
+            def_destsuffix = "git/"
+
+        destsuffix = ud.parm.get("destsuffix", def_destsuffix)
+        destdir = ud.destdir = os.path.join(destdir, destsuffix)
+        if os.path.exists(destdir):
+            bb.utils.prunedir(destdir)
+
+        cloneflags = "-s -n"
+        if ud.bareclone:
+            cloneflags += " --mirror"
+
+        # Versions of git prior to 1.7.9.2 have issues where foo.git and foo get confused
+        # and you end up with some horrible union of the two when you attempt to clone it
+        # The least invasive workaround seems to be a symlink to the real directory to
+        # fool git into ignoring any .git version that may also be present.
+        #
+        # The issue is fixed in more recent versions of git so we can drop this hack in future
+        # when that version becomes common enough.
+        clonedir = ud.clonedir
+        if not ud.path.endswith(".git"):
+            indirectiondir = destdir[:-1] + ".indirectionsymlink"
+            if os.path.exists(indirectiondir):
+                os.remove(indirectiondir)
+            bb.utils.mkdirhier(os.path.dirname(indirectiondir))
+            os.symlink(ud.clonedir, indirectiondir)
+            clonedir = indirectiondir
+
+        runfetchcmd("%s clone %s %s/ %s" % (ud.basecmd, cloneflags, clonedir, destdir), d)
+        os.chdir(destdir)
+        repourl = self._get_repo_url(ud)
+        runfetchcmd("%s remote set-url origin %s" % (ud.basecmd, repourl), d)
+        if not ud.nocheckout:
+            if subdir != "":
+                runfetchcmd("%s read-tree %s%s" % (ud.basecmd, ud.revisions[ud.names[0]], readpathspec), d)
+                runfetchcmd("%s checkout-index -q -f -a" % ud.basecmd, d)
+            elif not ud.nobranch:
+                branchname =  ud.branches[ud.names[0]]
+                runfetchcmd("%s checkout -B %s %s" % (ud.basecmd, branchname, \
+                            ud.revisions[ud.names[0]]), d)
+                runfetchcmd("%s branch --set-upstream %s origin/%s" % (ud.basecmd, branchname, \
+                            branchname), d)
+            else:
+                runfetchcmd("%s checkout %s" % (ud.basecmd, ud.revisions[ud.names[0]]), d)
+
+        return True
+
+    def clean(self, ud, d):
+        """ clean the git directory """
+
+        bb.utils.remove(ud.localpath, True)
+        bb.utils.remove(ud.fullmirror)
+        bb.utils.remove(ud.fullmirror + ".done")
+
+    def supports_srcrev(self):
+        return True
+
+    def _contains_ref(self, ud, d, name):
+        cmd = ""
+        if ud.nobranch:
+            cmd = "%s log --pretty=oneline -n 1 %s -- 2> /dev/null | wc -l" % (
+                ud.basecmd, ud.revisions[name])
+        else:
+            cmd =  "%s branch --contains %s --list %s 2> /dev/null | wc -l" % (
+                ud.basecmd, ud.revisions[name], ud.branches[name])
+        try:
+            output = runfetchcmd(cmd, d, quiet=True)
+        except bb.fetch2.FetchError:
+            return False
+        if len(output.split()) > 1:
+            raise bb.fetch2.FetchError("The command '%s' gave output with more then 1 line unexpectedly, output: '%s'" % (cmd, output))
+        return output.split()[0] != "0"
+
+    def _get_repo_url(self, ud):
+        """
+        Return the repository URL
+        """
+        if ud.user:
+            username = ud.user + '@'
+        else:
+            username = ""
+        return "%s://%s%s%s" % (ud.proto, username, ud.host, ud.path)
+
+    def _revision_key(self, ud, d, name):
+        """
+        Return a unique key for the url
+        """
+        return "git:" + ud.host + ud.path.replace('/', '.') + ud.unresolvedrev[name]
+
+    def _lsremote(self, ud, d, search):
+        """
+        Run git ls-remote with the specified search string
+        """
+        repourl = self._get_repo_url(ud)
+        cmd = "%s ls-remote %s %s" % \
+              (ud.basecmd, repourl, search)
+        if ud.proto.lower() != 'file':
+            bb.fetch2.check_network_access(d, cmd)
+        output = runfetchcmd(cmd, d, True)
+        if not output:
+            raise bb.fetch2.FetchError("The command %s gave empty output unexpectedly" % cmd, ud.url)
+        return output
+
+    def _latest_revision(self, ud, d, name):
+        """
+        Compute the HEAD revision for the url
+        """
+        output = self._lsremote(ud, d, "")
+        # Tags of the form ^{} may not work, need to fallback to other form
+        if ud.unresolvedrev[name][:5] == "refs/":
+            head = ud.unresolvedrev[name]
+            tag = ud.unresolvedrev[name]
+        else:
+            head = "refs/heads/%s" % ud.unresolvedrev[name]
+            tag = "refs/tags/%s" % ud.unresolvedrev[name]
+        for s in [head, tag + "^{}", tag]:
+            for l in output.split('\n'):
+                if s in l:
+                    return l.split()[0]
+        raise bb.fetch2.FetchError("Unable to resolve '%s' in upstream git repository in git ls-remote output for %s" % \
+            (ud.unresolvedrev[name], ud.host+ud.path))
+
+    def latest_versionstring(self, ud, d):
+        """
+        Compute the latest release name like "x.y.x" in "x.y.x+gitHASH"
+        by searching through the tags output of ls-remote, comparing
+        versions and returning the highest match.
+        """
+        pupver = ('', '')
+
+        tagregex = re.compile(d.getVar('GITTAGREGEX', True) or "(?P<pver>([0-9][\.|_]?)+)")
+        try:
+            output = self._lsremote(ud, d, "refs/tags/*")
+        except bb.fetch2.FetchError or bb.fetch2.NetworkAccess:
+            return pupver
+
+        verstring = ""
+        revision = ""
+        for line in output.split("\n"):
+            if not line:
+                break
+
+            tag_head = line.split("/")[-1]
+            # Ignore non-released branches
+            m = re.search("(alpha|beta|rc|final)+", tag_head)
+            if m:
+                continue
+
+            # search for version in the line
+            tag = tagregex.search(tag_head)
+            if tag == None:
+                continue
+
+            tag = tag.group('pver')
+            tag = tag.replace("_", ".")
+
+            if verstring and bb.utils.vercmp(("0", tag, ""), ("0", verstring, "")) < 0:
+                continue
+
+            verstring = tag
+            revision = line.split()[0]
+            pupver = (verstring, revision)
+
+        return pupver
+
+    def _build_revision(self, ud, d, name):
+        return ud.revisions[name]
+
+    def gitpkgv_revision(self, ud, d, name):
+        """
+        Return a sortable revision number by counting commits in the history
+        Based on gitpkgv.bblass in meta-openembedded
+        """
+        rev = self._build_revision(ud, d, name)
+        localpath = ud.localpath
+        rev_file = os.path.join(localpath, "oe-gitpkgv_" + rev)
+        if not os.path.exists(localpath):
+            commits = None
+        else:
+            if not os.path.exists(rev_file) or not os.path.getsize(rev_file):
+                from pipes import quote
+                commits = bb.fetch2.runfetchcmd(
+                        "git rev-list %s -- | wc -l" % (quote(rev)),
+                        d, quiet=True).strip().lstrip('0')
+                if commits:
+                    open(rev_file, "w").write("%d\n" % int(commits))
+            else:
+                commits = open(rev_file, "r").readline(128).strip()
+        if commits:
+            return False, "%s+%s" % (commits, rev[:7])
+        else:
+            return True, str(rev)
+
+    def checkstatus(self, fetch, ud, d):
+        try:
+            self._lsremote(ud, d, "")
+            return True
+        except FetchError:
+            return False
diff --git a/bitbake/lib/bb/fetch2/gitannex.py b/bitbake/lib/bb/fetch2/gitannex.py
new file mode 100644
index 0000000..0f37897
--- /dev/null
+++ b/bitbake/lib/bb/fetch2/gitannex.py
@@ -0,0 +1,76 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake 'Fetch' git annex implementation
+"""
+
+# Copyright (C) 2014 Otavio Salvador
+# Copyright (C) 2014 O.S. Systems Software LTDA.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import os
+import bb
+from   bb import data
+from   bb.fetch2.git import Git
+from   bb.fetch2 import runfetchcmd
+from   bb.fetch2 import logger
+
+class GitANNEX(Git):
+    def supports(self, ud, d):
+        """
+        Check to see if a given url can be fetched with git.
+        """
+        return ud.type in ['gitannex']
+
+    def uses_annex(self, ud, d):
+        for name in ud.names:
+            try:
+                runfetchcmd("%s rev-list git-annex" % (ud.basecmd), d, quiet=True)
+                return True
+            except bb.fetch.FetchError:
+                pass
+
+        return False
+
+    def update_annex(self, ud, d):
+        try:
+            runfetchcmd("%s annex get --all" % (ud.basecmd), d, quiet=True)
+        except bb.fetch.FetchError:
+            return False
+        runfetchcmd("chmod u+w -R %s/annex" % (ud.clonedir), d, quiet=True)
+
+        return True
+
+    def download(self, ud, d):
+        Git.download(self, ud, d)
+
+        os.chdir(ud.clonedir)
+        annex = self.uses_annex(ud, d)
+        if annex:
+            self.update_annex(ud, d)
+
+    def unpack(self, ud, destdir, d):
+        Git.unpack(self, ud, destdir, d)
+
+        os.chdir(ud.destdir)
+        try:
+            runfetchcmd("%s annex sync" % (ud.basecmd), d)
+        except bb.fetch.FetchError:
+            pass
+
+        annex = self.uses_annex(ud, d)
+        if annex:
+            runfetchcmd("%s annex get" % (ud.basecmd), d)
+            runfetchcmd("chmod u+w -R %s/.git/annex" % (ud.destdir), d, quiet=True)
diff --git a/bitbake/lib/bb/fetch2/gitsm.py b/bitbake/lib/bb/fetch2/gitsm.py
new file mode 100644
index 0000000..0392e48
--- /dev/null
+++ b/bitbake/lib/bb/fetch2/gitsm.py
@@ -0,0 +1,137 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake 'Fetch' git submodules implementation
+
+Inherits from and extends the Git fetcher to retrieve submodules of a git repository
+after cloning.
+
+SRC_URI = "gitsm://<see Git fetcher for syntax>"
+
+See the Git fetcher, git://, for usage documentation.
+
+NOTE: Switching a SRC_URI from "git://" to "gitsm://" requires a clean of your recipe.
+
+"""
+
+# Copyright (C) 2013 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import os
+import bb
+from   bb    import data
+from   bb.fetch2.git import Git
+from   bb.fetch2 import runfetchcmd
+from   bb.fetch2 import logger
+
+class GitSM(Git):
+    def supports(self, ud, d):
+        """
+        Check to see if a given url can be fetched with git.
+        """
+        return ud.type in ['gitsm']
+
+    def uses_submodules(self, ud, d):
+        for name in ud.names:
+            try:
+                runfetchcmd("%s show %s:.gitmodules" % (ud.basecmd, ud.revisions[name]), d, quiet=True)
+                return True
+            except bb.fetch.FetchError:
+                pass
+        return False
+
+    def _set_relative_paths(self, repopath):
+        """
+        Fix submodule paths to be relative instead of absolute,
+        so that when we move the repo it doesn't break
+        (In Git 1.7.10+ this is done automatically)
+        """
+        submodules = []
+        with open(os.path.join(repopath, '.gitmodules'), 'r') as f:
+            for line in f.readlines():
+                if line.startswith('[submodule'):
+                    submodules.append(line.split('"')[1])
+
+        for module in submodules:
+            repo_conf = os.path.join(repopath, module, '.git')
+            if os.path.exists(repo_conf):
+                with open(repo_conf, 'r') as f:
+                    lines = f.readlines()
+                newpath = ''
+                for i, line in enumerate(lines):
+                    if line.startswith('gitdir:'):
+                        oldpath = line.split(': ')[-1].rstrip()
+                        if oldpath.startswith('/'):
+                            newpath = '../' * (module.count('/') + 1) + '.git/modules/' + module
+                            lines[i] = 'gitdir: %s\n' % newpath
+                            break
+                if newpath:
+                    with open(repo_conf, 'w') as f:
+                        for line in lines:
+                            f.write(line)
+
+            repo_conf2 = os.path.join(repopath, '.git', 'modules', module, 'config')
+            if os.path.exists(repo_conf2):
+                with open(repo_conf2, 'r') as f:
+                    lines = f.readlines()
+                newpath = ''
+                for i, line in enumerate(lines):
+                    if line.lstrip().startswith('worktree = '):
+                        oldpath = line.split(' = ')[-1].rstrip()
+                        if oldpath.startswith('/'):
+                            newpath = '../' * (module.count('/') + 3) + module
+                            lines[i] = '\tworktree = %s\n' % newpath
+                            break
+                if newpath:
+                    with open(repo_conf2, 'w') as f:
+                        for line in lines:
+                            f.write(line)
+
+    def update_submodules(self, ud, d):
+        # We have to convert bare -> full repo, do the submodule bit, then convert back
+        tmpclonedir = ud.clonedir + ".tmp"
+        gitdir = tmpclonedir + os.sep + ".git"
+        bb.utils.remove(tmpclonedir, True)
+        os.mkdir(tmpclonedir)
+        os.rename(ud.clonedir, gitdir)
+        runfetchcmd("sed " + gitdir + "/config -i -e 's/bare.*=.*true/bare = false/'", d)
+        os.chdir(tmpclonedir)
+        runfetchcmd(ud.basecmd + " reset --hard", d)
+        runfetchcmd(ud.basecmd + " checkout " + ud.revisions[ud.names[0]], d)
+        runfetchcmd(ud.basecmd + " submodule init", d)
+        runfetchcmd(ud.basecmd + " submodule update", d)
+        self._set_relative_paths(tmpclonedir)
+        runfetchcmd("sed " + gitdir + "/config -i -e 's/bare.*=.*false/bare = true/'", d)
+        os.rename(gitdir, ud.clonedir,)
+        bb.utils.remove(tmpclonedir, True)
+
+    def download(self, ud, d):
+        Git.download(self, ud, d)
+
+        os.chdir(ud.clonedir)
+        submodules = self.uses_submodules(ud, d)
+        if submodules:
+            self.update_submodules(ud, d)
+
+    def unpack(self, ud, destdir, d):
+        Git.unpack(self, ud, destdir, d)
+        
+        os.chdir(ud.destdir)
+        submodules = self.uses_submodules(ud, d)
+        if submodules:
+            runfetchcmd("cp -r " + ud.clonedir + "/modules " + ud.destdir + "/.git/", d)
+            runfetchcmd(ud.basecmd + " submodule init", d)
+            runfetchcmd(ud.basecmd + " submodule update", d)
+
diff --git a/bitbake/lib/bb/fetch2/hg.py b/bitbake/lib/bb/fetch2/hg.py
new file mode 100644
index 0000000..d978630
--- /dev/null
+++ b/bitbake/lib/bb/fetch2/hg.py
@@ -0,0 +1,275 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake 'Fetch' implementation for mercurial DRCS (hg).
+
+"""
+
+# Copyright (C) 2003, 2004  Chris Larson
+# Copyright (C) 2004        Marcin Juszkiewicz
+# Copyright (C) 2007        Robert Schuster
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Based on functions from the base bb module, Copyright 2003 Holger Schurig
+
+import os
+import sys
+import logging
+import bb
+from bb import data
+from bb.fetch2 import FetchMethod
+from bb.fetch2 import FetchError
+from bb.fetch2 import MissingParameterError
+from bb.fetch2 import runfetchcmd
+from bb.fetch2 import logger
+
+class Hg(FetchMethod):
+    """Class to fetch from mercurial repositories"""
+    def supports(self, ud, d):
+        """
+        Check to see if a given url can be fetched with mercurial.
+        """
+        return ud.type in ['hg']
+
+    def supports_checksum(self, urldata):
+        """
+        Don't require checksums for local archives created from
+        repository checkouts.
+        """ 
+        return False
+
+    def urldata_init(self, ud, d):
+        """
+        init hg specific variable within url data
+        """
+        if not "module" in ud.parm:
+            raise MissingParameterError('module', ud.url)
+
+        ud.module = ud.parm["module"]
+
+        if 'protocol' in ud.parm:
+            ud.proto = ud.parm['protocol']
+        elif not ud.host:
+            ud.proto = 'file'
+        else:
+            ud.proto = "hg"
+
+        ud.setup_revisons(d)
+
+        if 'rev' in ud.parm:
+            ud.revision = ud.parm['rev']
+        elif not ud.revision:
+            ud.revision = self.latest_revision(ud, d)
+
+        # Create paths to mercurial checkouts
+        hgsrcname = '%s_%s_%s' % (ud.module.replace('/', '.'), \
+                            ud.host, ud.path.replace('/', '.'))
+        ud.mirrortarball = 'hg_%s.tar.gz' % hgsrcname
+        ud.fullmirror = os.path.join(d.getVar("DL_DIR", True), ud.mirrortarball)
+
+        hgdir = d.getVar("HGDIR", True) or (d.getVar("DL_DIR", True) + "/hg/")
+        ud.pkgdir = os.path.join(hgdir, hgsrcname)
+        ud.moddir = os.path.join(ud.pkgdir, ud.module)
+        ud.localfile = ud.moddir
+        ud.basecmd = data.getVar("FETCHCMD_hg", d, True) or "/usr/bin/env hg"
+
+        ud.write_tarballs = d.getVar("BB_GENERATE_MIRROR_TARBALLS", True)
+
+    def need_update(self, ud, d):
+        revTag = ud.parm.get('rev', 'tip')
+        if revTag == "tip":
+            return True
+        if not os.path.exists(ud.localpath):
+            return True
+        return False
+
+    def try_premirror(self, ud, d):
+        # If we don't do this, updating an existing checkout with only premirrors
+        # is not possible
+        if d.getVar("BB_FETCH_PREMIRRORONLY", True) is not None:
+            return True
+        if os.path.exists(ud.moddir):
+            return False
+        return True
+
+    def _buildhgcommand(self, ud, d, command):
+        """
+        Build up an hg commandline based on ud
+        command is "fetch", "update", "info"
+        """
+
+        proto = ud.parm.get('protocol', 'http')
+
+        host = ud.host
+        if proto == "file":
+            host = "/"
+            ud.host = "localhost"
+
+        if not ud.user:
+            hgroot = host + ud.path
+        else:
+            if ud.pswd:
+                hgroot = ud.user + ":" + ud.pswd + "@" + host + ud.path
+            else:
+                hgroot = ud.user + "@" + host + ud.path
+
+        if command == "info":
+            return "%s identify -i %s://%s/%s" % (ud.basecmd, proto, hgroot, ud.module)
+
+        options = [];
+
+        # Don't specify revision for the fetch; clone the entire repo.
+        # This avoids an issue if the specified revision is a tag, because
+        # the tag actually exists in the specified revision + 1, so it won't
+        # be available when used in any successive commands.
+        if ud.revision and command != "fetch":
+            options.append("-r %s" % ud.revision)
+
+        if command == "fetch":
+            if ud.user and ud.pswd:
+                cmd = "%s --config auth.default.prefix=* --config auth.default.username=%s --config auth.default.password=%s --config \"auth.default.schemes=%s\" clone %s %s://%s/%s %s" % (ud.basecmd, ud.user, ud.pswd, proto, " ".join(options), proto, hgroot, ud.module, ud.module)
+            else:
+                cmd = "%s clone %s %s://%s/%s %s" % (ud.basecmd, " ".join(options), proto, hgroot, ud.module, ud.module)
+        elif command == "pull":
+            # do not pass options list; limiting pull to rev causes the local
+            # repo not to contain it and immediately following "update" command
+            # will crash
+            if ud.user and ud.pswd:
+                cmd = "%s --config auth.default.prefix=* --config auth.default.username=%s --config auth.default.password=%s --config \"auth.default.schemes=%s\" pull" % (ud.basecmd, ud.user, ud.pswd, proto)
+            else:
+                cmd = "%s pull" % (ud.basecmd)
+        elif command == "update":
+            if ud.user and ud.pswd:
+                cmd = "%s --config auth.default.prefix=* --config auth.default.username=%s --config auth.default.password=%s --config \"auth.default.schemes=%s\" update -C %s" % (ud.basecmd, ud.user, ud.pswd, proto, " ".join(options))
+            else:
+                cmd = "%s update -C %s" % (ud.basecmd, " ".join(options))
+        else:
+            raise FetchError("Invalid hg command %s" % command, ud.url)
+
+        return cmd
+
+    def download(self, ud, d):
+        """Fetch url"""
+
+        ud.repochanged = not os.path.exists(ud.fullmirror)
+
+        logger.debug(2, "Fetch: checking for module directory '" + ud.moddir + "'")
+
+        # If the checkout doesn't exist and the mirror tarball does, extract it
+        if not os.path.exists(ud.pkgdir) and os.path.exists(ud.fullmirror):
+            bb.utils.mkdirhier(ud.pkgdir)
+            os.chdir(ud.pkgdir)
+            runfetchcmd("tar -xzf %s" % (ud.fullmirror), d)
+
+        if os.access(os.path.join(ud.moddir, '.hg'), os.R_OK):
+            # Found the source, check whether need pull
+            updatecmd = self._buildhgcommand(ud, d, "update")
+            os.chdir(ud.moddir)
+            logger.debug(1, "Running %s", updatecmd)
+            try:
+                runfetchcmd(updatecmd, d)
+            except bb.fetch2.FetchError:
+                # Runnning pull in the repo
+                pullcmd = self._buildhgcommand(ud, d, "pull")
+                logger.info("Pulling " + ud.url)
+                # update sources there
+                os.chdir(ud.moddir)
+                logger.debug(1, "Running %s", pullcmd)
+                bb.fetch2.check_network_access(d, pullcmd, ud.url)
+                runfetchcmd(pullcmd, d)
+                ud.repochanged = True
+
+        # No source found, clone it.
+        if not os.path.exists(ud.moddir):
+            fetchcmd = self._buildhgcommand(ud, d, "fetch")
+            logger.info("Fetch " + ud.url)
+            # check out sources there
+            bb.utils.mkdirhier(ud.pkgdir)
+            os.chdir(ud.pkgdir)
+            logger.debug(1, "Running %s", fetchcmd)
+            bb.fetch2.check_network_access(d, fetchcmd, ud.url)
+            runfetchcmd(fetchcmd, d)
+
+        # Even when we clone (fetch), we still need to update as hg's clone
+        # won't checkout the specified revision if its on a branch
+        updatecmd = self._buildhgcommand(ud, d, "update")
+        os.chdir(ud.moddir)
+        logger.debug(1, "Running %s", updatecmd)
+        runfetchcmd(updatecmd, d)
+
+    def clean(self, ud, d):
+        """ Clean the hg dir """
+
+        bb.utils.remove(ud.localpath, True)
+        bb.utils.remove(ud.fullmirror)
+        bb.utils.remove(ud.fullmirror + ".done")
+
+    def supports_srcrev(self):
+        return True
+
+    def _latest_revision(self, ud, d, name):
+        """
+        Compute tip revision for the url
+        """
+        bb.fetch2.check_network_access(d, self._buildhgcommand(ud, d, "info"))
+        output = runfetchcmd(self._buildhgcommand(ud, d, "info"), d)
+        return output.strip()
+
+    def _build_revision(self, ud, d, name):
+        return ud.revision
+
+    def _revision_key(self, ud, d, name):
+        """
+        Return a unique key for the url
+        """
+        return "hg:" + ud.moddir
+
+    def build_mirror_data(self, ud, d):
+        # Generate a mirror tarball if needed
+        if ud.write_tarballs == "1" and (ud.repochanged or not os.path.exists(ud.fullmirror)):
+            # it's possible that this symlink points to read-only filesystem with PREMIRROR
+            if os.path.islink(ud.fullmirror):
+                os.unlink(ud.fullmirror)
+
+            os.chdir(ud.pkgdir)
+            logger.info("Creating tarball of hg repository")
+            runfetchcmd("tar -czf %s %s" % (ud.fullmirror, ud.module), d)
+            runfetchcmd("touch %s.done" % (ud.fullmirror), d)
+
+    def localpath(self, ud, d):
+        return ud.pkgdir
+
+    def unpack(self, ud, destdir, d):
+        """
+        Make a local clone or export for the url
+        """
+
+        revflag = "-r %s" % ud.revision
+        subdir = ud.parm.get("destsuffix", ud.module)
+        codir = "%s/%s" % (destdir, subdir)
+
+        scmdata = ud.parm.get("scmdata", "")
+        if scmdata != "nokeep":
+            if not os.access(os.path.join(codir, '.hg'), os.R_OK):
+                logger.debug(2, "Unpack: creating new hg repository in '" + codir + "'")
+                runfetchcmd("%s init %s" % (ud.basecmd, codir), d)
+            logger.debug(2, "Unpack: updating source in '" + codir + "'")
+            os.chdir(codir)
+            runfetchcmd("%s pull %s" % (ud.basecmd, ud.moddir), d)
+            runfetchcmd("%s up -C %s" % (ud.basecmd, revflag), d)
+        else:
+            logger.debug(2, "Unpack: extracting source to '" + codir + "'")
+            os.chdir(ud.moddir)
+            runfetchcmd("%s archive -t files %s %s" % (ud.basecmd, revflag, codir), d)
diff --git a/bitbake/lib/bb/fetch2/local.py b/bitbake/lib/bb/fetch2/local.py
new file mode 100644
index 0000000..2d921f7
--- /dev/null
+++ b/bitbake/lib/bb/fetch2/local.py
@@ -0,0 +1,128 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake 'Fetch' implementations
+
+Classes for obtaining upstream sources for the
+BitBake build tools.
+
+"""
+
+# Copyright (C) 2003, 2004  Chris Larson
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Based on functions from the base bb module, Copyright 2003 Holger Schurig
+
+import os
+import urllib
+import bb
+import bb.utils
+from   bb import data
+from   bb.fetch2 import FetchMethod, FetchError
+from   bb.fetch2 import logger
+
+class Local(FetchMethod):
+    def supports(self, urldata, d):
+        """
+        Check to see if a given url represents a local fetch.
+        """
+        return urldata.type in ['file']
+
+    def urldata_init(self, ud, d):
+        # We don't set localfile as for this fetcher the file is already local!
+        ud.decodedurl = urllib.unquote(ud.url.split("://")[1].split(";")[0])
+        ud.basename = os.path.basename(ud.decodedurl)
+        ud.basepath = ud.decodedurl
+        return
+
+    def localpath(self, urldata, d):
+        """
+        Return the local filename of a given url assuming a successful fetch.
+        """
+        return self.localpaths(urldata, d)[-1]
+
+    def localpaths(self, urldata, d):
+        """
+        Return the local filename of a given url assuming a successful fetch.
+        """
+        searched = []
+        path = urldata.decodedurl
+        newpath = path
+        if path[0] == "/":
+            return [path]
+        filespath = data.getVar('FILESPATH', d, True)
+        if filespath:
+            logger.debug(2, "Searching for %s in paths:\n    %s" % (path, "\n    ".join(filespath.split(":"))))
+            newpath, hist = bb.utils.which(filespath, path, history=True)
+            searched.extend(hist)
+        if not newpath:
+            filesdir = data.getVar('FILESDIR', d, True)
+            if filesdir:
+                logger.debug(2, "Searching for %s in path: %s" % (path, filesdir))
+                newpath = os.path.join(filesdir, path)
+                searched.append(newpath)
+        if (not newpath or not os.path.exists(newpath)) and path.find("*") != -1:
+            # For expressions using '*', best we can do is take the first directory in FILESPATH that exists
+            newpath, hist = bb.utils.which(filespath, ".", history=True)
+            searched.extend(hist)
+            logger.debug(2, "Searching for %s in path: %s" % (path, newpath))
+            return searched
+        if not os.path.exists(newpath):
+            dldirfile = os.path.join(d.getVar("DL_DIR", True), path)
+            logger.debug(2, "Defaulting to %s for %s" % (dldirfile, path))
+            bb.utils.mkdirhier(os.path.dirname(dldirfile))
+            searched.append(dldirfile)
+            return searched
+        return searched
+
+    def need_update(self, ud, d):
+        if ud.url.find("*") != -1:
+            return False
+        if os.path.exists(ud.localpath):
+            return False
+        return True
+
+    def download(self, urldata, d):
+        """Fetch urls (no-op for Local method)"""
+        # no need to fetch local files, we'll deal with them in place.
+        if self.supports_checksum(urldata) and not os.path.exists(urldata.localpath):
+            locations = []
+            filespath = data.getVar('FILESPATH', d, True)
+            if filespath:
+                locations = filespath.split(":")
+            filesdir = data.getVar('FILESDIR', d, True)
+            if filesdir:
+                locations.append(filesdir)
+            locations.append(d.getVar("DL_DIR", True))
+
+            msg = "Unable to find file " + urldata.url + " anywhere. The paths that were searched were:\n    " + "\n    ".join(locations)
+            raise FetchError(msg)
+
+        return True
+
+    def checkstatus(self, fetch, urldata, d):
+        """
+        Check the status of the url
+        """
+        if urldata.localpath.find("*") != -1:
+            logger.info("URL %s looks like a glob and was therefore not checked.", urldata.url)
+            return True
+        if os.path.exists(urldata.localpath):
+            return True
+        return False
+
+    def clean(self, urldata, d):
+        return
+
diff --git a/bitbake/lib/bb/fetch2/osc.py b/bitbake/lib/bb/fetch2/osc.py
new file mode 100644
index 0000000..3d87796
--- /dev/null
+++ b/bitbake/lib/bb/fetch2/osc.py
@@ -0,0 +1,135 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+Bitbake "Fetch" implementation for osc (Opensuse build service client).
+Based on the svn "Fetch" implementation.
+
+"""
+
+import  os
+import  sys
+import logging
+import  bb
+from    bb       import data
+from    bb.fetch2 import FetchMethod
+from    bb.fetch2 import FetchError
+from    bb.fetch2 import MissingParameterError
+from    bb.fetch2 import runfetchcmd
+
+class Osc(FetchMethod):
+    """Class to fetch a module or modules from Opensuse build server
+       repositories."""
+
+    def supports(self, ud, d):
+        """
+        Check to see if a given url can be fetched with osc.
+        """
+        return ud.type in ['osc']
+
+    def urldata_init(self, ud, d):
+        if not "module" in ud.parm:
+            raise MissingParameterError('module', ud.url)
+
+        ud.module = ud.parm["module"]
+
+        # Create paths to osc checkouts
+        relpath = self._strip_leading_slashes(ud.path)
+        ud.pkgdir = os.path.join(data.expand('${OSCDIR}', d), ud.host)
+        ud.moddir = os.path.join(ud.pkgdir, relpath, ud.module)
+
+        if 'rev' in ud.parm:
+            ud.revision = ud.parm['rev']
+        else:
+            pv = data.getVar("PV", d, 0)
+            rev = bb.fetch2.srcrev_internal_helper(ud, d)
+            if rev and rev != True:
+                ud.revision = rev
+            else:
+                ud.revision = ""
+
+        ud.localfile = data.expand('%s_%s_%s.tar.gz' % (ud.module.replace('/', '.'), ud.path.replace('/', '.'), ud.revision), d)
+
+    def _buildosccommand(self, ud, d, command):
+        """
+        Build up an ocs commandline based on ud
+        command is "fetch", "update", "info"
+        """
+
+        basecmd = data.expand('${FETCHCMD_osc}', d)
+
+        proto = ud.parm.get('protocol', 'ocs')
+
+        options = []
+
+        config = "-c %s" % self.generate_config(ud, d)
+
+        if ud.revision:
+            options.append("-r %s" % ud.revision)
+
+        coroot = self._strip_leading_slashes(ud.path)
+
+        if command == "fetch":
+            osccmd = "%s %s co %s/%s %s" % (basecmd, config, coroot, ud.module, " ".join(options))
+        elif command == "update":
+            osccmd = "%s %s up %s" % (basecmd, config, " ".join(options))
+        else:
+            raise FetchError("Invalid osc command %s" % command, ud.url)
+
+        return osccmd
+
+    def download(self, ud, d):
+        """
+        Fetch url
+        """
+
+        logger.debug(2, "Fetch: checking for module directory '" + ud.moddir + "'")
+
+        if os.access(os.path.join(data.expand('${OSCDIR}', d), ud.path, ud.module), os.R_OK):
+            oscupdatecmd = self._buildosccommand(ud, d, "update")
+            logger.info("Update "+ ud.url)
+            # update sources there
+            os.chdir(ud.moddir)
+            logger.debug(1, "Running %s", oscupdatecmd)
+            bb.fetch2.check_network_access(d, oscupdatecmd, ud.url)
+            runfetchcmd(oscupdatecmd, d)
+        else:
+            oscfetchcmd = self._buildosccommand(ud, d, "fetch")
+            logger.info("Fetch " + ud.url)
+            # check out sources there
+            bb.utils.mkdirhier(ud.pkgdir)
+            os.chdir(ud.pkgdir)
+            logger.debug(1, "Running %s", oscfetchcmd)
+            bb.fetch2.check_network_access(d, oscfetchcmd, ud.url)
+            runfetchcmd(oscfetchcmd, d)
+
+        os.chdir(os.path.join(ud.pkgdir + ud.path))
+        # tar them up to a defined filename
+        runfetchcmd("tar -czf %s %s" % (ud.localpath, ud.module), d, cleanup = [ud.localpath])
+
+    def supports_srcrev(self):
+        return False
+
+    def generate_config(self, ud, d):
+        """
+        Generate a .oscrc to be used for this run.
+        """
+
+        config_path = os.path.join(data.expand('${OSCDIR}', d), "oscrc")
+        if (os.path.exists(config_path)):
+            os.remove(config_path)
+
+        f = open(config_path, 'w')
+        f.write("[general]\n")
+        f.write("apisrv = %s\n" % ud.host)
+        f.write("scheme = http\n")
+        f.write("su-wrapper = su -c\n")
+        f.write("build-root = %s\n" % data.expand('${WORKDIR}', d))
+        f.write("urllist = http://moblin-obs.jf.intel.com:8888/build/%(project)s/%(repository)s/%(buildarch)s/:full/%(name)s.rpm\n")
+        f.write("extra-pkgs = gzip\n")
+        f.write("\n")
+        f.write("[%s]\n" % ud.host)
+        f.write("user = %s\n" % ud.parm["user"])
+        f.write("pass = %s\n" % ud.parm["pswd"])
+        f.close()
+
+        return config_path
diff --git a/bitbake/lib/bb/fetch2/perforce.py b/bitbake/lib/bb/fetch2/perforce.py
new file mode 100644
index 0000000..3a10c7c
--- /dev/null
+++ b/bitbake/lib/bb/fetch2/perforce.py
@@ -0,0 +1,187 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake 'Fetch' implementations
+
+Classes for obtaining upstream sources for the
+BitBake build tools.
+
+"""
+
+# Copyright (C) 2003, 2004  Chris Larson
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Based on functions from the base bb module, Copyright 2003 Holger Schurig
+
+from future_builtins import zip
+import os
+import subprocess
+import logging
+import bb
+from   bb import data
+from   bb.fetch2 import FetchMethod
+from   bb.fetch2 import FetchError
+from   bb.fetch2 import logger
+from   bb.fetch2 import runfetchcmd
+
+class Perforce(FetchMethod):
+    def supports(self, ud, d):
+        return ud.type in ['p4']
+
+    def doparse(url, d):
+        parm = {}
+        path = url.split("://")[1]
+        delim = path.find("@");
+        if delim != -1:
+            (user, pswd, host, port) = path.split('@')[0].split(":")
+            path = path.split('@')[1]
+        else:
+            (host, port) = d.getVar('P4PORT', False).split(':')
+            user = ""
+            pswd = ""
+
+        if path.find(";") != -1:
+            keys=[]
+            values=[]
+            plist = path.split(';')
+            for item in plist:
+                if item.count('='):
+                    (key, value) = item.split('=')
+                    keys.append(key)
+                    values.append(value)
+
+            parm = dict(zip(keys, values))
+        path = "//" + path.split(';')[0]
+        host += ":%s" % (port)
+        parm["cset"] = Perforce.getcset(d, path, host, user, pswd, parm)
+
+        return host, path, user, pswd, parm
+    doparse = staticmethod(doparse)
+
+    def getcset(d, depot, host, user, pswd, parm):
+        p4opt = ""
+        if "cset" in parm:
+            return parm["cset"];
+        if user:
+            p4opt += " -u %s" % (user)
+        if pswd:
+            p4opt += " -P %s" % (pswd)
+        if host:
+            p4opt += " -p %s" % (host)
+
+        p4date = d.getVar("P4DATE", True)
+        if "revision" in parm:
+            depot += "#%s" % (parm["revision"])
+        elif "label" in parm:
+            depot += "@%s" % (parm["label"])
+        elif p4date:
+            depot += "@%s" % (p4date)
+
+        p4cmd = d.getVar('FETCHCMD_p4', True) or "p4"
+        logger.debug(1, "Running %s%s changes -m 1 %s", p4cmd, p4opt, depot)
+        p4file, errors = bb.process.run("%s%s changes -m 1 %s" % (p4cmd, p4opt, depot))
+        cset = p4file.strip()
+        logger.debug(1, "READ %s", cset)
+        if not cset:
+            return -1
+
+        return cset.split(' ')[1]
+    getcset = staticmethod(getcset)
+
+    def urldata_init(self, ud, d):
+        (host, path, user, pswd, parm) = Perforce.doparse(ud.url, d)
+
+        base_path = path.replace('/...', '')
+        base_path = self._strip_leading_slashes(base_path)
+        
+        if "label" in parm:
+            version = parm["label"]
+        else:
+            version = Perforce.getcset(d, path, host, user, pswd, parm)
+
+        ud.localfile = data.expand('%s+%s+%s.tar.gz' % (host, base_path.replace('/', '.'), version), d)
+
+    def download(self, ud, d):
+        """
+        Fetch urls
+        """
+
+        (host, depot, user, pswd, parm) = Perforce.doparse(ud.url, d)
+
+        if depot.find('/...') != -1:
+            path = depot[:depot.find('/...')]
+        else:
+            path = depot[:depot.rfind('/')]
+
+        module = parm.get('module', os.path.basename(path))
+
+        # Get the p4 command
+        p4opt = ""
+        if user:
+            p4opt += " -u %s" % (user)
+
+        if pswd:
+            p4opt += " -P %s" % (pswd)
+
+        if host:
+            p4opt += " -p %s" % (host)
+
+        p4cmd = d.getVar('FETCHCMD_p4', True) or "p4"
+
+        # create temp directory
+        logger.debug(2, "Fetch: creating temporary directory")
+        bb.utils.mkdirhier(d.expand('${WORKDIR}'))
+        mktemp = d.getVar("FETCHCMD_p4mktemp", True) or d.expand("mktemp -d -q '${WORKDIR}/oep4.XXXXXX'")
+        tmpfile, errors = bb.process.run(mktemp)
+        tmpfile = tmpfile.strip()
+        if not tmpfile:
+            raise FetchError("Fetch: unable to create temporary directory.. make sure 'mktemp' is in the PATH.", ud.url)
+
+        if "label" in parm:
+            depot = "%s@%s" % (depot, parm["label"])
+        else:
+            cset = Perforce.getcset(d, depot, host, user, pswd, parm)
+            depot = "%s@%s" % (depot, cset)
+
+        os.chdir(tmpfile)
+        logger.info("Fetch " + ud.url)
+        logger.info("%s%s files %s", p4cmd, p4opt, depot)
+        p4file, errors = bb.process.run("%s%s files %s" % (p4cmd, p4opt, depot))
+        p4file = [f.rstrip() for f in p4file.splitlines()]
+
+        if not p4file:
+            raise FetchError("Fetch: unable to get the P4 files from %s" % depot, ud.url)
+
+        count = 0
+
+        for file in p4file:
+            list = file.split()
+
+            if list[2] == "delete":
+                continue
+
+            dest = list[0][len(path)+1:]
+            where = dest.find("#")
+
+            subprocess.call("%s%s print -o %s/%s %s" % (p4cmd, p4opt, module, dest[:where], list[0]), shell=True)
+            count = count + 1
+
+        if count == 0:
+            logger.error()
+            raise FetchError("Fetch: No files gathered from the P4 fetch", ud.url)
+
+        runfetchcmd("tar -czf %s %s" % (ud.localpath, module), d, cleanup = [ud.localpath])
+        # cleanup
+        bb.utils.prunedir(tmpfile)
diff --git a/bitbake/lib/bb/fetch2/repo.py b/bitbake/lib/bb/fetch2/repo.py
new file mode 100644
index 0000000..21678eb
--- /dev/null
+++ b/bitbake/lib/bb/fetch2/repo.py
@@ -0,0 +1,98 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake "Fetch" repo (git) implementation
+
+"""
+
+# Copyright (C) 2009 Tom Rini <trini@embeddedalley.com>
+#
+# Based on git.py which is:
+#Copyright (C) 2005 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import os
+import bb
+from   bb    import data
+from   bb.fetch2 import FetchMethod
+from   bb.fetch2 import runfetchcmd
+
+class Repo(FetchMethod):
+    """Class to fetch a module or modules from repo (git) repositories"""
+    def supports(self, ud, d):
+        """
+        Check to see if a given url can be fetched with repo.
+        """
+        return ud.type in ["repo"]
+
+    def urldata_init(self, ud, d):
+        """
+        We don"t care about the git rev of the manifests repository, but
+        we do care about the manifest to use.  The default is "default".
+        We also care about the branch or tag to be used.  The default is
+        "master".
+        """
+
+        ud.proto = ud.parm.get('protocol', 'git')
+        ud.branch = ud.parm.get('branch', 'master')
+        ud.manifest = ud.parm.get('manifest', 'default.xml')
+        if not ud.manifest.endswith('.xml'):
+            ud.manifest += '.xml'
+
+        ud.localfile = data.expand("repo_%s%s_%s_%s.tar.gz" % (ud.host, ud.path.replace("/", "."), ud.manifest, ud.branch), d)
+
+    def download(self, ud, d):
+        """Fetch url"""
+
+        if os.access(os.path.join(data.getVar("DL_DIR", d, True), ud.localfile), os.R_OK):
+            logger.debug(1, "%s already exists (or was stashed). Skipping repo init / sync.", ud.localpath)
+            return
+
+        gitsrcname = "%s%s" % (ud.host, ud.path.replace("/", "."))
+        repodir = data.getVar("REPODIR", d, True) or os.path.join(data.getVar("DL_DIR", d, True), "repo")
+        codir = os.path.join(repodir, gitsrcname, ud.manifest)
+
+        if ud.user:
+            username = ud.user + "@"
+        else:
+            username = ""
+
+        bb.utils.mkdirhier(os.path.join(codir, "repo"))
+        os.chdir(os.path.join(codir, "repo"))
+        if not os.path.exists(os.path.join(codir, "repo", ".repo")):
+            bb.fetch2.check_network_access(d, "repo init -m %s -b %s -u %s://%s%s%s" % (ud.manifest, ud.branch, ud.proto, username, ud.host, ud.path), ud.url)
+            runfetchcmd("repo init -m %s -b %s -u %s://%s%s%s" % (ud.manifest, ud.branch, ud.proto, username, ud.host, ud.path), d)
+
+        bb.fetch2.check_network_access(d, "repo sync %s" % ud.url, ud.url)
+        runfetchcmd("repo sync", d)
+        os.chdir(codir)
+
+        scmdata = ud.parm.get("scmdata", "")
+        if scmdata == "keep":
+            tar_flags = ""
+        else:
+            tar_flags = "--exclude '.repo' --exclude '.git'"
+
+        # Create a cache
+        runfetchcmd("tar %s -czf %s %s" % (tar_flags, ud.localpath, os.path.join(".", "*") ), d)
+
+    def supports_srcrev(self):
+        return False
+
+    def _build_revision(self, ud, d):
+        return ud.manifest
+
+    def _want_sortable_revision(self, ud, d):
+        return False
diff --git a/bitbake/lib/bb/fetch2/sftp.py b/bitbake/lib/bb/fetch2/sftp.py
new file mode 100644
index 0000000..cb2f753
--- /dev/null
+++ b/bitbake/lib/bb/fetch2/sftp.py
@@ -0,0 +1,129 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake SFTP Fetch implementation
+
+Class for fetching files via SFTP. It tries to adhere to the (now
+expired) IETF Internet Draft for "Uniform Resource Identifier (URI)
+Scheme for Secure File Transfer Protocol (SFTP) and Secure Shell
+(SSH)" (SECSH URI).
+
+It uses SFTP (as to adhere to the SECSH URI specification). It only
+supports key based authentication, not password. This class, unlike
+the SSH fetcher, does not support fetching a directory tree from the
+remote.
+
+  http://tools.ietf.org/html/draft-ietf-secsh-scp-sftp-ssh-uri-04
+  https://www.iana.org/assignments/uri-schemes/prov/sftp
+  https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13
+
+Please note that '/' is used as host path seperator, and not ":"
+as you may be used to from the scp/sftp commands. You can use a
+~ (tilde) to specify a path relative to your home directory.
+(The /~user/ syntax, for specyfing a path relative to another
+user's home directory is not supported.) Note that the tilde must
+still follow the host path seperator ("/"). See exampels below.
+
+Example SRC_URIs:
+
+SRC_URI = "sftp://host.example.com/dir/path.file.txt"
+
+A path relative to your home directory.
+
+SRC_URI = "sftp://host.example.com/~/dir/path.file.txt"
+
+You can also specify a username (specyfing password in the
+URI is not supported, use SSH keys to authenticate):
+
+SRC_URI = "sftp://user@host.example.com/dir/path.file.txt"
+
+"""
+
+# Copyright (C) 2013, Olof Johansson <olof.johansson@axis.com>
+#
+# Based in part on bb.fetch2.wget:
+#    Copyright (C) 2003, 2004  Chris Larson
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Based on functions from the base bb module, Copyright 2003 Holger Schurig
+
+import os
+import bb
+import urllib
+import commands
+from bb import data
+from bb.fetch2 import URI
+from bb.fetch2 import FetchMethod
+from bb.fetch2 import runfetchcmd
+
+
+class SFTP(FetchMethod):
+    """Class to fetch urls via 'sftp'"""
+
+    def supports(self, ud, d):
+        """
+        Check to see if a given url can be fetched with sftp.
+        """
+        return ud.type in ['sftp']
+
+    def recommends_checksum(self, urldata):
+        return True
+
+    def urldata_init(self, ud, d):
+        if 'protocol' in ud.parm and ud.parm['protocol'] == 'git':
+            raise bb.fetch2.ParameterError(
+                "Invalid protocol - if you wish to fetch from a " +
+                "git repository using ssh, you need to use the " +
+                "git:// prefix with protocol=ssh", ud.url)
+
+        if 'downloadfilename' in ud.parm:
+            ud.basename = ud.parm['downloadfilename']
+        else:
+            ud.basename = os.path.basename(ud.path)
+
+        ud.localfile = data.expand(urllib.unquote(ud.basename), d)
+
+    def download(self, ud, d):
+        """Fetch urls"""
+
+        urlo = URI(ud.url)
+        basecmd = 'sftp -oBatchMode=yes'
+        port = ''
+        if urlo.port:
+            port = '-P %d' % urlo.port
+            urlo.port = None
+
+        dldir = data.getVar('DL_DIR', d, True)
+        lpath = os.path.join(dldir, ud.localfile)
+
+        user = ''
+        if urlo.userinfo:
+            user = urlo.userinfo + '@'
+
+        path = urlo.path
+
+        # Supoprt URIs relative to the user's home directory, with
+        # the tilde syntax. (E.g. <sftp://example.com/~/foo.diff>).
+        if path[:3] == '/~/':
+            path = path[3:]
+
+        remote = '%s%s:%s' % (user, urlo.hostname, path)
+
+        cmd = '%s %s %s %s' % (basecmd, port, commands.mkarg(remote),
+                               commands.mkarg(lpath))
+
+        bb.fetch2.check_network_access(d, cmd, ud.url)
+        runfetchcmd(cmd, d)
+        return True
diff --git a/bitbake/lib/bb/fetch2/ssh.py b/bitbake/lib/bb/fetch2/ssh.py
new file mode 100644
index 0000000..635578a
--- /dev/null
+++ b/bitbake/lib/bb/fetch2/ssh.py
@@ -0,0 +1,128 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+'''
+BitBake 'Fetch' implementations
+
+This implementation is for Secure Shell (SSH), and attempts to comply with the
+IETF secsh internet draft:
+    http://tools.ietf.org/wg/secsh/draft-ietf-secsh-scp-sftp-ssh-uri/
+
+    Currently does not support the sftp parameters, as this uses scp
+    Also does not support the 'fingerprint' connection parameter.
+
+    Please note that '/' is used as host, path separator not ':' as you may 
+    be used to, also '~' can be used to specify user HOME, but again after '/'
+    
+    Example SRC_URI:
+    SRC_URI = "ssh://user@host.example.com/dir/path/file.txt"
+    SRC_URI = "ssh://user@host.example.com/~/file.txt"
+'''
+
+# Copyright (C) 2006  OpenedHand Ltd.
+#
+#
+# Based in part on svk.py:
+#    Copyright (C) 2006 Holger Hans Peter Freyther
+#    Based on svn.py:
+#        Copyright (C) 2003, 2004  Chris Larson
+#        Based on functions from the base bb module:
+#            Copyright 2003 Holger Schurig
+#
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import re, os
+from   bb import data
+from   bb.fetch2 import FetchMethod
+from   bb.fetch2 import FetchError
+from   bb.fetch2 import logger
+from   bb.fetch2 import runfetchcmd
+
+
+__pattern__ = re.compile(r'''
+ \s*                 # Skip leading whitespace
+ ssh://              # scheme
+ (                   # Optional username/password block
+  (?P<user>\S+)      # username
+  (:(?P<pass>\S+))?  # colon followed by the password (optional)
+ )?
+ (?P<cparam>(;[^;]+)*)?  # connection parameters block (optional)
+ @
+ (?P<host>\S+?)          # non-greedy match of the host
+ (:(?P<port>[0-9]+))?    # colon followed by the port (optional)
+ /
+ (?P<path>[^;]+)         # path on the remote system, may be absolute or relative,
+                         # and may include the use of '~' to reference the remote home
+                         # directory
+ (?P<sparam>(;[^;]+)*)?  # parameters block (optional)
+ $
+''', re.VERBOSE)
+
+class SSH(FetchMethod):
+    '''Class to fetch a module or modules via Secure Shell'''
+
+    def supports(self, urldata, d):
+        return __pattern__.match(urldata.url) != None
+
+    def supports_checksum(self, urldata):
+        return False
+
+    def urldata_init(self, urldata, d):
+        if 'protocol' in urldata.parm and urldata.parm['protocol'] == 'git':
+            raise bb.fetch2.ParameterError(
+                "Invalid protocol - if you wish to fetch from a git " +
+                "repository using ssh, you need to use " +
+                "git:// prefix with protocol=ssh", urldata.url)
+        m = __pattern__.match(urldata.url)
+        path = m.group('path')
+        host = m.group('host')
+        urldata.localpath = os.path.join(d.getVar('DL_DIR', True),
+                os.path.basename(os.path.normpath(path)))
+
+    def download(self, urldata, d):
+        dldir = d.getVar('DL_DIR', True)
+
+        m = __pattern__.match(urldata.url)
+        path = m.group('path')
+        host = m.group('host')
+        port = m.group('port')
+        user = m.group('user')
+        password = m.group('pass')
+
+        if port:
+            portarg = '-P %s' % port
+        else:
+            portarg = ''
+
+        if user:
+            fr = user
+            if password:
+                fr += ':%s' % password
+            fr += '@%s' % host
+        else:
+            fr = host
+        fr += ':%s' % path
+
+
+        import commands
+        cmd = 'scp -B -r %s %s %s/' % (
+            portarg,
+            commands.mkarg(fr),
+            commands.mkarg(dldir)
+        )
+
+        bb.fetch2.check_network_access(d, cmd, urldata.url)
+
+        runfetchcmd(cmd, d)
+
diff --git a/bitbake/lib/bb/fetch2/svn.py b/bitbake/lib/bb/fetch2/svn.py
new file mode 100644
index 0000000..1733c2b
--- /dev/null
+++ b/bitbake/lib/bb/fetch2/svn.py
@@ -0,0 +1,192 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake 'Fetch' implementation for svn.
+
+"""
+
+# Copyright (C) 2003, 2004  Chris Larson
+# Copyright (C) 2004        Marcin Juszkiewicz
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Based on functions from the base bb module, Copyright 2003 Holger Schurig
+
+import os
+import sys
+import logging
+import bb
+import re
+from   bb import data
+from   bb.fetch2 import FetchMethod
+from   bb.fetch2 import FetchError
+from   bb.fetch2 import MissingParameterError
+from   bb.fetch2 import runfetchcmd
+from   bb.fetch2 import logger
+
+class Svn(FetchMethod):
+    """Class to fetch a module or modules from svn repositories"""
+    def supports(self, ud, d):
+        """
+        Check to see if a given url can be fetched with svn.
+        """
+        return ud.type in ['svn']
+
+    def urldata_init(self, ud, d):
+        """
+        init svn specific variable within url data
+        """
+        if not "module" in ud.parm:
+            raise MissingParameterError('module', ud.url)
+
+        ud.basecmd = d.getVar('FETCHCMD_svn', True)
+
+        ud.module = ud.parm["module"]
+
+        # Create paths to svn checkouts
+        relpath = self._strip_leading_slashes(ud.path)
+        ud.pkgdir = os.path.join(data.expand('${SVNDIR}', d), ud.host, relpath)
+        ud.moddir = os.path.join(ud.pkgdir, ud.module)
+
+        ud.setup_revisons(d)
+
+        if 'rev' in ud.parm:
+            ud.revision = ud.parm['rev']
+
+        ud.localfile = data.expand('%s_%s_%s_%s_.tar.gz' % (ud.module.replace('/', '.'), ud.host, ud.path.replace('/', '.'), ud.revision), d)
+
+    def _buildsvncommand(self, ud, d, command):
+        """
+        Build up an svn commandline based on ud
+        command is "fetch", "update", "info"
+        """
+
+        proto = ud.parm.get('protocol', 'svn')
+
+        svn_rsh = None
+        if proto == "svn+ssh" and "rsh" in ud.parm:
+            svn_rsh = ud.parm["rsh"]
+
+        svnroot = ud.host + ud.path
+
+        options = []
+
+        options.append("--no-auth-cache")
+
+        if ud.user:
+            options.append("--username %s" % ud.user)
+
+        if ud.pswd:
+            options.append("--password %s" % ud.pswd)
+
+        if command == "info":
+            svncmd = "%s info %s %s://%s/%s/" % (ud.basecmd, " ".join(options), proto, svnroot, ud.module)
+        elif command == "log1":
+            svncmd = "%s log --limit 1 %s %s://%s/%s/" % (ud.basecmd, " ".join(options), proto, svnroot, ud.module)
+        else:
+            suffix = ""
+            if ud.revision:
+                options.append("-r %s" % ud.revision)
+                suffix = "@%s" % (ud.revision)
+
+            if command == "fetch":
+                transportuser = ud.parm.get("transportuser", "")
+                svncmd = "%s co %s %s://%s%s/%s%s %s" % (ud.basecmd, " ".join(options), proto, transportuser, svnroot, ud.module, suffix, ud.module)
+            elif command == "update":
+                svncmd = "%s update %s" % (ud.basecmd, " ".join(options))
+            else:
+                raise FetchError("Invalid svn command %s" % command, ud.url)
+
+        if svn_rsh:
+            svncmd = "svn_RSH=\"%s\" %s" % (svn_rsh, svncmd)
+
+        return svncmd
+
+    def download(self, ud, d):
+        """Fetch url"""
+
+        logger.debug(2, "Fetch: checking for module directory '" + ud.moddir + "'")
+
+        if os.access(os.path.join(ud.moddir, '.svn'), os.R_OK):
+            svnupdatecmd = self._buildsvncommand(ud, d, "update")
+            logger.info("Update " + ud.url)
+            # update sources there
+            os.chdir(ud.moddir)
+            # We need to attempt to run svn upgrade first in case its an older working format
+            try:
+                runfetchcmd(ud.basecmd + " upgrade", d)
+            except FetchError:
+                pass
+            logger.debug(1, "Running %s", svnupdatecmd)
+            bb.fetch2.check_network_access(d, svnupdatecmd, ud.url)
+            runfetchcmd(svnupdatecmd, d)
+        else:
+            svnfetchcmd = self._buildsvncommand(ud, d, "fetch")
+            logger.info("Fetch " + ud.url)
+            # check out sources there
+            bb.utils.mkdirhier(ud.pkgdir)
+            os.chdir(ud.pkgdir)
+            logger.debug(1, "Running %s", svnfetchcmd)
+            bb.fetch2.check_network_access(d, svnfetchcmd, ud.url)
+            runfetchcmd(svnfetchcmd, d)
+
+        scmdata = ud.parm.get("scmdata", "")
+        if scmdata == "keep":
+            tar_flags = ""
+        else:
+            tar_flags = "--exclude '.svn'"
+
+        os.chdir(ud.pkgdir)
+        # tar them up to a defined filename
+        runfetchcmd("tar %s -czf %s %s" % (tar_flags, ud.localpath, ud.module), d, cleanup = [ud.localpath])
+
+    def clean(self, ud, d):
+        """ Clean SVN specific files and dirs """
+
+        bb.utils.remove(ud.localpath)
+        bb.utils.remove(ud.moddir, True)
+        
+
+    def supports_srcrev(self):
+        return True
+
+    def _revision_key(self, ud, d, name):
+        """
+        Return a unique key for the url
+        """
+        return "svn:" + ud.moddir
+
+    def _latest_revision(self, ud, d, name):
+        """
+        Return the latest upstream revision number
+        """
+        bb.fetch2.check_network_access(d, self._buildsvncommand(ud, d, "log1"))
+
+        output = runfetchcmd("LANG=C LC_ALL=C " + self._buildsvncommand(ud, d, "log1"), d, True)
+
+        # skip the first line, as per output of svn log
+        # then we expect the revision on the 2nd line
+        revision = re.search('^r([0-9]*)', output.splitlines()[1]).group(1)
+
+        return revision
+
+    def sortable_revision(self, ud, d, name):
+        """
+        Return a sortable revision number which in our case is the revision number
+        """
+
+        return False, self._build_revision(ud, d)
+
+    def _build_revision(self, ud, d):
+        return ud.revision
diff --git a/bitbake/lib/bb/fetch2/wget.py b/bitbake/lib/bb/fetch2/wget.py
new file mode 100644
index 0000000..bd2a897
--- /dev/null
+++ b/bitbake/lib/bb/fetch2/wget.py
@@ -0,0 +1,541 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake 'Fetch' implementations
+
+Classes for obtaining upstream sources for the
+BitBake build tools.
+
+"""
+
+# Copyright (C) 2003, 2004  Chris Larson
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Based on functions from the base bb module, Copyright 2003 Holger Schurig
+
+import re
+import tempfile
+import subprocess
+import os
+import logging
+import bb
+import urllib
+from   bb import data
+from   bb.fetch2 import FetchMethod
+from   bb.fetch2 import FetchError
+from   bb.fetch2 import logger
+from   bb.fetch2 import runfetchcmd
+from   bs4 import BeautifulSoup
+
+class Wget(FetchMethod):
+    """Class to fetch urls via 'wget'"""
+    def supports(self, ud, d):
+        """
+        Check to see if a given url can be fetched with wget.
+        """
+        return ud.type in ['http', 'https', 'ftp']
+
+    def recommends_checksum(self, urldata):
+        return True
+
+    def urldata_init(self, ud, d):
+        if 'protocol' in ud.parm:
+            if ud.parm['protocol'] == 'git':
+                raise bb.fetch2.ParameterError("Invalid protocol - if you wish to fetch from a git repository using http, you need to instead use the git:// prefix with protocol=http", ud.url)
+
+        if 'downloadfilename' in ud.parm:
+            ud.basename = ud.parm['downloadfilename']
+        else:
+            ud.basename = os.path.basename(ud.path)
+
+        ud.localfile = data.expand(urllib.unquote(ud.basename), d)
+
+        self.basecmd = d.getVar("FETCHCMD_wget", True) or "/usr/bin/env wget -t 2 -T 30 -nv --passive-ftp --no-check-certificate"
+
+    def _runwget(self, ud, d, command, quiet):
+
+        logger.debug(2, "Fetching %s using command '%s'" % (ud.url, command))
+        bb.fetch2.check_network_access(d, command)
+        runfetchcmd(command, d, quiet)
+
+    def download(self, ud, d):
+        """Fetch urls"""
+
+        fetchcmd = self.basecmd
+
+        if 'downloadfilename' in ud.parm:
+            dldir = d.getVar("DL_DIR", True)
+            bb.utils.mkdirhier(os.path.dirname(dldir + os.sep + ud.localfile))
+            fetchcmd += " -O " + dldir + os.sep + ud.localfile
+
+        uri = ud.url.split(";")[0]
+        if os.path.exists(ud.localpath):
+            # file exists, but we didnt complete it.. trying again..
+            fetchcmd += d.expand(" -c -P ${DL_DIR} '%s'" % uri)
+        else:
+            fetchcmd += d.expand(" -P ${DL_DIR} '%s'" % uri)
+
+        self._runwget(ud, d, fetchcmd, False)
+
+        # Sanity check since wget can pretend it succeed when it didn't
+        # Also, this used to happen if sourceforge sent us to the mirror page
+        if not os.path.exists(ud.localpath):
+            raise FetchError("The fetch command returned success for url %s but %s doesn't exist?!" % (uri, ud.localpath), uri)
+
+        if os.path.getsize(ud.localpath) == 0:
+            os.remove(ud.localpath)
+            raise FetchError("The fetch of %s resulted in a zero size file?! Deleting and failing since this isn't right." % (uri), uri)
+
+        return True
+
+    def checkstatus(self, fetch, ud, d):
+        import urllib2, socket, httplib
+        from urllib import addinfourl
+        from bb.fetch2 import FetchConnectionCache
+
+        class HTTPConnectionCache(httplib.HTTPConnection):
+            if fetch.connection_cache:
+                def connect(self):
+                    """Connect to the host and port specified in __init__."""
+
+                    sock = fetch.connection_cache.get_connection(self.host, self.port)
+                    if sock:
+                        self.sock = sock
+                    else:
+                        self.sock = socket.create_connection((self.host, self.port),
+                                    self.timeout, self.source_address)
+                        fetch.connection_cache.add_connection(self.host, self.port, self.sock)
+
+                    if self._tunnel_host:
+                        self._tunnel()
+
+        class CacheHTTPHandler(urllib2.HTTPHandler):
+            def http_open(self, req):
+                return self.do_open(HTTPConnectionCache, req)
+
+            def do_open(self, http_class, req):
+                """Return an addinfourl object for the request, using http_class.
+
+                http_class must implement the HTTPConnection API from httplib.
+                The addinfourl return value is a file-like object.  It also
+                has methods and attributes including:
+                    - info(): return a mimetools.Message object for the headers
+                    - geturl(): return the original request URL
+                    - code: HTTP status code
+                """
+                host = req.get_host()
+                if not host:
+                    raise urlllib2.URLError('no host given')
+
+                h = http_class(host, timeout=req.timeout) # will parse host:port
+                h.set_debuglevel(self._debuglevel)
+
+                headers = dict(req.unredirected_hdrs)
+                headers.update(dict((k, v) for k, v in req.headers.items()
+                            if k not in headers))
+
+                # We want to make an HTTP/1.1 request, but the addinfourl
+                # class isn't prepared to deal with a persistent connection.
+                # It will try to read all remaining data from the socket,
+                # which will block while the server waits for the next request.
+                # So make sure the connection gets closed after the (only)
+                # request.
+
+                # Don't close connection when connection_cache is enabled,
+                if fetch.connection_cache is None: 
+                    headers["Connection"] = "close"
+                else:
+                    headers["Connection"] = "Keep-Alive" # Works for HTTP/1.0
+
+                headers = dict(
+                    (name.title(), val) for name, val in headers.items())
+
+                if req._tunnel_host:
+                    tunnel_headers = {}
+                    proxy_auth_hdr = "Proxy-Authorization"
+                    if proxy_auth_hdr in headers:
+                        tunnel_headers[proxy_auth_hdr] = headers[proxy_auth_hdr]
+                        # Proxy-Authorization should not be sent to origin
+                        # server.
+                        del headers[proxy_auth_hdr]
+                    h.set_tunnel(req._tunnel_host, headers=tunnel_headers)
+
+                try:
+                    h.request(req.get_method(), req.get_selector(), req.data, headers)
+                except socket.error, err: # XXX what error?
+                    # Don't close connection when cache is enabled.
+                    if fetch.connection_cache is None:
+                        h.close()
+                    raise urllib2.URLError(err)
+                else:
+                    try:
+                        r = h.getresponse(buffering=True)
+                    except TypeError: # buffering kw not supported
+                        r = h.getresponse()
+
+                # Pick apart the HTTPResponse object to get the addinfourl
+                # object initialized properly.
+
+                # Wrap the HTTPResponse object in socket's file object adapter
+                # for Windows.  That adapter calls recv(), so delegate recv()
+                # to read().  This weird wrapping allows the returned object to
+                # have readline() and readlines() methods.
+
+                # XXX It might be better to extract the read buffering code
+                # out of socket._fileobject() and into a base class.
+                r.recv = r.read
+
+                # no data, just have to read
+                r.read()
+                class fp_dummy(object):
+                    def read(self):
+                        return ""
+                    def readline(self):
+                        return ""
+                    def close(self):
+                        pass
+
+                resp = addinfourl(fp_dummy(), r.msg, req.get_full_url())
+                resp.code = r.status
+                resp.msg = r.reason
+
+                # Close connection when server request it.
+                if fetch.connection_cache is not None:
+                    if 'Connection' in r.msg and r.msg['Connection'] == 'close':
+                        fetch.connection_cache.remove_connection(h.host, h.port)
+
+                return resp
+
+        def export_proxies(d):
+            variables = ['http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY',
+                            'ftp_proxy', 'FTP_PROXY', 'no_proxy', 'NO_PROXY']
+            exported = False
+
+            for v in variables:
+                if v in os.environ.keys():
+                    exported = True
+                else:
+                    v_proxy = d.getVar(v, True)
+                    if v_proxy is not None:
+                        os.environ[v] = v_proxy
+                        exported = True
+
+            return exported
+
+        def head_method(self):
+            return "HEAD"
+
+        exported_proxies = export_proxies(d)
+
+        # XXX: Since Python 2.7.9 ssl cert validation is enabled by default
+        # see PEP-0476, this causes verification errors on some https servers
+        # so disable by default.
+        import ssl
+        ssl_context = None
+        if hasattr(ssl, '_create_unverified_context'):
+            ssl_context = ssl._create_unverified_context()
+
+        if exported_proxies == True and ssl_context is not None:
+            opener = urllib2.build_opener(urllib2.ProxyHandler, CacheHTTPHandler,
+                    urllib2.HTTPSHandler(context=ssl_context))
+        elif exported_proxies == False and ssl_context is not None:
+            opener = urllib2.build_opener(CacheHTTPHandler,
+                    urllib2.HTTPSHandler(context=ssl_context))
+        elif exported_proxies == True and ssl_context is None:
+            opener = urllib2.build_opener(urllib2.ProxyHandler, CacheHTTPHandler)
+        else:
+            opener = urllib2.build_opener(CacheHTTPHandler)
+
+        urllib2.Request.get_method = head_method
+        urllib2.install_opener(opener)
+
+        uri = ud.url.split(";")[0]
+
+        try:
+            urllib2.urlopen(uri)
+        except:
+            return False
+        return True
+
+    def _parse_path(self, regex, s):
+        """
+        Find and group name, version and archive type in the given string s
+        """
+
+        m = regex.search(s)
+        if m:
+            pname = ''
+            pver = ''
+            ptype = ''
+
+            mdict = m.groupdict()
+            if 'name' in mdict.keys():
+                pname = mdict['name']
+            if 'pver' in mdict.keys():
+                pver = mdict['pver']
+            if 'type' in mdict.keys():
+                ptype = mdict['type']
+
+            bb.debug(3, "_parse_path: %s, %s, %s" % (pname, pver, ptype))
+
+            return (pname, pver, ptype)
+
+        return None
+
+    def _modelate_version(self, version):
+        if version[0] in ['.', '-']:
+            if version[1].isdigit():
+                version = version[1] + version[0] + version[2:len(version)]
+            else:
+                version = version[1:len(version)]
+
+        version = re.sub('-', '.', version)
+        version = re.sub('_', '.', version)
+        version = re.sub('(rc)+', '.1000.', version)
+        version = re.sub('(beta)+', '.100.', version)
+        version = re.sub('(alpha)+', '.10.', version)
+        if version[0] == 'v':
+            version = version[1:len(version)]
+        return version
+
+    def _vercmp(self, old, new):
+        """
+        Check whether 'new' is newer than 'old' version. We use existing vercmp() for the
+        purpose. PE is cleared in comparison as it's not for build, and PR is cleared too
+        for simplicity as it's somehow difficult to get from various upstream format
+        """
+
+        (oldpn, oldpv, oldsuffix) = old
+        (newpn, newpv, newsuffix) = new
+
+        """
+        Check for a new suffix type that we have never heard of before
+        """
+        if (newsuffix):
+            m = self.suffix_regex_comp.search(newsuffix)
+            if not m:
+                bb.warn("%s has a possible unknown suffix: %s" % (newpn, newsuffix))
+                return False
+
+        """
+        Not our package so ignore it
+        """
+        if oldpn != newpn:
+            return False
+
+        oldpv = self._modelate_version(oldpv)
+        newpv = self._modelate_version(newpv)
+
+        return bb.utils.vercmp(("0", oldpv, ""), ("0", newpv, ""))
+
+    def _fetch_index(self, uri, ud, d):
+        """
+        Run fetch checkstatus to get directory information
+        """
+        f = tempfile.NamedTemporaryFile()
+
+        agent = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.12) Gecko/20101027 Ubuntu/9.10 (karmic) Firefox/3.6.12"
+        fetchcmd = self.basecmd
+        fetchcmd += " -O " + f.name + " --user-agent='" + agent + "' '" + uri + "'"
+        try:
+            self._runwget(ud, d, fetchcmd, True)
+            fetchresult = f.read()
+        except bb.fetch2.BBFetchException:
+            fetchresult = ""
+
+        f.close()
+        return fetchresult
+
+    def _check_latest_version(self, url, package, package_regex, current_version, ud, d):
+        """
+        Return the latest version of a package inside a given directory path
+        If error or no version, return ""
+        """
+        valid = 0
+        version = ['', '', '']
+
+        bb.debug(3, "VersionURL: %s" % (url))
+        soup = BeautifulSoup(self._fetch_index(url, ud, d))
+        if not soup:
+            bb.debug(3, "*** %s NO SOUP" % (url))
+            return ""
+
+        for line in soup.find_all('a', href=True):
+            bb.debug(3, "line['href'] = '%s'" % (line['href']))
+            bb.debug(3, "line = '%s'" % (str(line)))
+
+            newver = self._parse_path(package_regex, line['href'])
+            if not newver:
+                newver = self._parse_path(package_regex, str(line))
+
+            if newver:
+                bb.debug(3, "Upstream version found: %s" % newver[1])
+                if valid == 0:
+                    version = newver
+                    valid = 1
+                elif self._vercmp(version, newver) < 0:
+                    version = newver
+                
+        pupver = re.sub('_', '.', version[1])
+
+        bb.debug(3, "*** %s -> UpstreamVersion = %s (CurrentVersion = %s)" %
+                (package, pupver or "N/A", current_version[1]))
+
+        if valid:
+            return pupver
+
+        return ""
+
+    def _check_latest_version_by_dir(self, dirver, package, package_regex,
+            current_version, ud, d):
+        """
+            Scan every directory in order to get upstream version.
+        """
+        version_dir = ['', '', '']
+        version = ['', '', '']
+
+        dirver_regex = re.compile("(\D*)((\d+[\.\-_])+(\d+))")
+        s = dirver_regex.search(dirver)
+        if s:
+            version_dir[1] = s.group(2)
+        else:
+            version_dir[1] = dirver
+
+        dirs_uri = bb.fetch.encodeurl([ud.type, ud.host,
+                ud.path.split(dirver)[0], ud.user, ud.pswd, {}])
+        bb.debug(3, "DirURL: %s, %s" % (dirs_uri, package))
+
+        soup = BeautifulSoup(self._fetch_index(dirs_uri, ud, d))
+        if not soup:
+            return version[1]
+
+        for line in soup.find_all('a', href=True):
+            s = dirver_regex.search(line['href'].strip("/"))
+            if s:
+                version_dir_new = ['', s.group(2), '']
+                if self._vercmp(version_dir, version_dir_new) <= 0:
+                    dirver_new = s.group(1) + s.group(2)
+                    path = ud.path.replace(dirver, dirver_new, True) \
+                        .split(package)[0]
+                    uri = bb.fetch.encodeurl([ud.type, ud.host, path,
+                        ud.user, ud.pswd, {}])
+
+                    pupver = self._check_latest_version(uri,
+                            package, package_regex, current_version, ud, d)
+                    if pupver:
+                        version[1] = pupver
+
+                    version_dir = version_dir_new
+
+        return version[1]
+
+    def _init_regexes(self, package, ud, d):
+        """
+        Match as many patterns as possible such as:
+                gnome-common-2.20.0.tar.gz (most common format)
+                gtk+-2.90.1.tar.gz
+                xf86-input-synaptics-12.6.9.tar.gz
+                dri2proto-2.3.tar.gz
+                blktool_4.orig.tar.gz
+                libid3tag-0.15.1b.tar.gz
+                unzip552.tar.gz
+                icu4c-3_6-src.tgz
+                genext2fs_1.3.orig.tar.gz
+                gst-fluendo-mp3
+        """
+        # match most patterns which uses "-" as separator to version digits
+        pn_prefix1 = "[a-zA-Z][a-zA-Z0-9]*([-_][a-zA-Z]\w+)*\+?[-_]"
+        # a loose pattern such as for unzip552.tar.gz
+        pn_prefix2 = "[a-zA-Z]+"
+        # a loose pattern such as for 80325-quicky-0.4.tar.gz
+        pn_prefix3 = "[0-9]+[-]?[a-zA-Z]+"
+        # Save the Package Name (pn) Regex for use later
+        pn_regex = "(%s|%s|%s)" % (pn_prefix1, pn_prefix2, pn_prefix3)
+
+        # match version
+        pver_regex = "(([A-Z]*\d+[a-zA-Z]*[\.\-_]*)+)"
+
+        # match arch
+        parch_regex = "-source|_all_"
+
+        # src.rpm extension was added only for rpm package. Can be removed if the rpm
+        # packaged will always be considered as having to be manually upgraded
+        psuffix_regex = "(tar\.gz|tgz|tar\.bz2|zip|xz|rpm|bz2|orig\.tar\.gz|tar\.xz|src\.tar\.gz|src\.tgz|svnr\d+\.tar\.bz2|stable\.tar\.gz|src\.rpm)"
+
+        # match name, version and archive type of a package
+        package_regex_comp = re.compile("(?P<name>%s?\.?v?)(?P<pver>%s)(?P<arch>%s)?[\.-](?P<type>%s$)"
+                                                    % (pn_regex, pver_regex, parch_regex, psuffix_regex))
+        self.suffix_regex_comp = re.compile(psuffix_regex)
+
+        # compile regex, can be specific by package or generic regex
+        pn_regex = d.getVar('REGEX', True)
+        if pn_regex:
+            package_custom_regex_comp = re.compile(pn_regex)
+        else:
+            version = self._parse_path(package_regex_comp, package)
+            if version:
+                package_custom_regex_comp = re.compile(
+                    "(?P<name>%s)(?P<pver>%s)(?P<arch>%s)?[\.-](?P<type>%s)" %
+                    (re.escape(version[0]), pver_regex, parch_regex, psuffix_regex))
+            else:
+                package_custom_regex_comp = None
+
+        return package_custom_regex_comp
+
+    def latest_versionstring(self, ud, d):
+        """
+        Manipulate the URL and try to obtain the latest package version
+
+        sanity check to ensure same name and type.
+        """
+        package = ud.path.split("/")[-1]
+        current_version = ['', d.getVar('PV', True), '']
+
+        """possible to have no version in pkg name, such as spectrum-fw"""
+        if not re.search("\d+", package):
+            current_version[1] = re.sub('_', '.', current_version[1])
+            current_version[1] = re.sub('-', '.', current_version[1])
+            return (current_version[1], '')
+
+        package_regex = self._init_regexes(package, ud, d)
+        if package_regex is None:
+            bb.warn("latest_versionstring: package %s don't match pattern" % (package))
+            return ('', '')
+        bb.debug(3, "latest_versionstring, regex: %s" % (package_regex.pattern))
+
+        uri = ""
+        regex_uri = d.getVar("REGEX_URI", True)
+        if not regex_uri:
+            path = ud.path.split(package)[0]
+
+            # search for version matches on folders inside the path, like:
+            # "5.7" in http://download.gnome.org/sources/${PN}/5.7/${PN}-${PV}.tar.gz
+            dirver_regex = re.compile("(?P<dirver>[^/]*(\d+\.)*\d+([-_]r\d+)*)/")
+            m = dirver_regex.search(path)
+            if m:
+                pn = d.getVar('PN', True)
+                dirver = m.group('dirver')
+
+                dirver_pn_regex = re.compile("%s\d?" % (re.escape(pn)))
+                if not dirver_pn_regex.search(dirver):
+                    return (self._check_latest_version_by_dir(dirver,
+                        package, package_regex, current_version, ud, d), '')
+
+            uri = bb.fetch.encodeurl([ud.type, ud.host, path, ud.user, ud.pswd, {}])
+        else:
+            uri = regex_uri
+
+        return (self._check_latest_version(uri, package, package_regex,
+                current_version, ud, d), '')
diff --git a/bitbake/lib/bb/main.py b/bitbake/lib/bb/main.py
new file mode 100755
index 0000000..8762f72
--- /dev/null
+++ b/bitbake/lib/bb/main.py
@@ -0,0 +1,427 @@
+#!/usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Copyright (C) 2003, 2004  Chris Larson
+# Copyright (C) 2003, 2004  Phil Blundell
+# Copyright (C) 2003 - 2005 Michael 'Mickey' Lauer
+# Copyright (C) 2005        Holger Hans Peter Freyther
+# Copyright (C) 2005        ROAD GmbH
+# Copyright (C) 2006        Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import os
+import sys
+import logging
+import optparse
+import warnings
+
+import bb
+from bb import event
+import bb.msg
+from bb import cooker
+from bb import ui
+from bb import server
+from bb import cookerdata
+
+logger = logging.getLogger("BitBake")
+
+class BBMainException(Exception):
+    pass
+
+def present_options(optionlist):
+    if len(optionlist) > 1:
+        return ' or '.join([', '.join(optionlist[:-1]), optionlist[-1]])
+    else:
+        return optionlist[0]
+
+class BitbakeHelpFormatter(optparse.IndentedHelpFormatter):
+    def format_option(self, option):
+        # We need to do this here rather than in the text we supply to
+        # add_option() because we don't want to call list_extension_modules()
+        # on every execution (since it imports all of the modules)
+        # Note also that we modify option.help rather than the returned text
+        # - this is so that we don't have to re-format the text ourselves
+        if option.dest == 'ui':
+            valid_uis = list_extension_modules(bb.ui, 'main')
+            option.help = option.help.replace('@CHOICES@', present_options(valid_uis))
+        elif option.dest == 'servertype':
+            valid_server_types = list_extension_modules(bb.server, 'BitBakeServer')
+            option.help = option.help.replace('@CHOICES@', present_options(valid_server_types))
+
+        return optparse.IndentedHelpFormatter.format_option(self, option)
+
+def list_extension_modules(pkg, checkattr):
+    """
+    Lists extension modules in a specific Python package
+    (e.g. UIs, servers). NOTE: Calling this function will import all of the
+    submodules of the specified module in order to check for the specified
+    attribute; this can have unusual side-effects. As a result, this should
+    only be called when displaying help text or error messages.
+    Parameters:
+        pkg: previously imported Python package to list
+        checkattr: attribute to look for in module to determine if it's valid
+            as the type of extension you are looking for
+    """
+    import pkgutil
+    pkgdir = os.path.dirname(pkg.__file__)
+
+    modules = []
+    for _, modulename, _ in pkgutil.iter_modules([pkgdir]):
+        if os.path.isdir(os.path.join(pkgdir, modulename)):
+            # ignore directories
+            continue
+        try:
+            module = __import__(pkg.__name__, fromlist=[modulename])
+        except:
+            # If we can't import it, it's not valid
+            continue
+        module_if = getattr(module, modulename)
+        if getattr(module_if, 'hidden_extension', False):
+            continue
+        if not checkattr or hasattr(module_if, checkattr):
+            modules.append(modulename)
+    return modules
+
+def import_extension_module(pkg, modulename, checkattr):
+    try:
+        # Dynamically load the UI based on the ui name. Although we
+        # suggest a fixed set this allows you to have flexibility in which
+        # ones are available.
+        module = __import__(pkg.__name__, fromlist = [modulename])
+        return getattr(module, modulename)
+    except AttributeError:
+        raise BBMainException('FATAL: Unable to import extension module "%s" from %s. Valid extension modules: %s' % (modulename, pkg.__name__, present_options(list_extension_modules(pkg, checkattr))))
+
+
+# Display bitbake/OE warnings via the BitBake.Warnings logger, ignoring others"""
+warnlog = logging.getLogger("BitBake.Warnings")
+_warnings_showwarning = warnings.showwarning
+def _showwarning(message, category, filename, lineno, file=None, line=None):
+    if file is not None:
+        if _warnings_showwarning is not None:
+            _warnings_showwarning(message, category, filename, lineno, file, line)
+    else:
+        s = warnings.formatwarning(message, category, filename, lineno)
+        warnlog.warn(s)
+
+warnings.showwarning = _showwarning
+warnings.filterwarnings("ignore")
+warnings.filterwarnings("default", module="(<string>$|(oe|bb)\.)")
+warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
+warnings.filterwarnings("ignore", category=ImportWarning)
+warnings.filterwarnings("ignore", category=DeprecationWarning, module="<string>$")
+warnings.filterwarnings("ignore", message="With-statements now directly support multiple context managers")
+
+class BitBakeConfigParameters(cookerdata.ConfigParameters):
+
+    def parseCommandLine(self, argv=sys.argv):
+        parser = optparse.OptionParser(
+            formatter = BitbakeHelpFormatter(),
+            version = "BitBake Build Tool Core version %s" % bb.__version__,
+            usage = """%prog [options] [recipename/target recipe:do_task ...]
+
+    Executes the specified task (default is 'build') for a given set of target recipes (.bb files).
+    It is assumed there is a conf/bblayers.conf available in cwd or in BBPATH which
+    will provide the layer, BBFILES and other configuration information.""")
+
+        parser.add_option("-b", "--buildfile", help = "Execute tasks from a specific .bb recipe directly. WARNING: Does not handle any dependencies from other recipes.",
+                   action = "store", dest = "buildfile", default = None)
+
+        parser.add_option("-k", "--continue", help = "Continue as much as possible after an error. While the target that failed and anything depending on it cannot be built, as much as possible will be built before stopping.",
+                   action = "store_false", dest = "abort", default = True)
+
+        parser.add_option("-a", "--tryaltconfigs", help = "Continue with builds by trying to use alternative providers where possible.",
+                   action = "store_true", dest = "tryaltconfigs", default = False)
+
+        parser.add_option("-f", "--force", help = "Force the specified targets/task to run (invalidating any existing stamp file).",
+                   action = "store_true", dest = "force", default = False)
+
+        parser.add_option("-c", "--cmd", help = "Specify the task to execute. The exact options available depend on the metadata. Some examples might be 'compile' or 'populate_sysroot' or 'listtasks' may give a list of the tasks available.",
+                   action = "store", dest = "cmd")
+
+        parser.add_option("-C", "--clear-stamp", help = "Invalidate the stamp for the specified task such as 'compile' and then run the default task for the specified target(s).",
+                    action = "store", dest = "invalidate_stamp")
+
+        parser.add_option("-r", "--read", help = "Read the specified file before bitbake.conf.",
+                   action = "append", dest = "prefile", default = [])
+
+        parser.add_option("-R", "--postread", help = "Read the specified file after bitbake.conf.",
+                          action = "append", dest = "postfile", default = [])
+
+        parser.add_option("-v", "--verbose", help = "Output more log message data to the terminal.",
+                   action = "store_true", dest = "verbose", default = False)
+
+        parser.add_option("-D", "--debug", help = "Increase the debug level. You can specify this more than once.",
+                   action = "count", dest="debug", default = 0)
+
+        parser.add_option("-n", "--dry-run", help = "Don't execute, just go through the motions.",
+                   action = "store_true", dest = "dry_run", default = False)
+
+        parser.add_option("-S", "--dump-signatures", help = "Dump out the signature construction information, with no task execution. The SIGNATURE_HANDLER parameter is passed to the handler. Two common values are none and printdiff but the handler may define more/less. none means only dump the signature, printdiff means compare the dumped signature with the cached one.",
+                   action = "append", dest = "dump_signatures", default = [], metavar="SIGNATURE_HANDLER")
+
+        parser.add_option("-p", "--parse-only", help = "Quit after parsing the BB recipes.",
+                   action = "store_true", dest = "parse_only", default = False)
+
+        parser.add_option("-s", "--show-versions", help = "Show current and preferred versions of all recipes.",
+                   action = "store_true", dest = "show_versions", default = False)
+
+        parser.add_option("-e", "--environment", help = "Show the global or per-recipe environment complete with information about where variables were set/changed.",
+                   action = "store_true", dest = "show_environment", default = False)
+
+        parser.add_option("-g", "--graphviz", help = "Save dependency tree information for the specified targets in the dot syntax.",
+                    action = "store_true", dest = "dot_graph", default = False)
+
+        parser.add_option("-I", "--ignore-deps", help = """Assume these dependencies don't exist and are already provided (equivalent to ASSUME_PROVIDED). Useful to make dependency graphs more appealing""",
+                    action = "append", dest = "extra_assume_provided", default = [])
+
+        parser.add_option("-l", "--log-domains", help = """Show debug logging for the specified logging domains""",
+                    action = "append", dest = "debug_domains", default = [])
+
+        parser.add_option("-P", "--profile", help = "Profile the command and save reports.",
+                   action = "store_true", dest = "profile", default = False)
+
+        env_ui = os.environ.get('BITBAKE_UI', None)
+        default_ui = env_ui or 'knotty'
+        # @CHOICES@ is substituted out by BitbakeHelpFormatter above
+        parser.add_option("-u", "--ui", help = "The user interface to use (@CHOICES@ - default %default).",
+                   action="store", dest="ui", default=default_ui)
+
+        # @CHOICES@ is substituted out by BitbakeHelpFormatter above
+        parser.add_option("-t", "--servertype", help = "Choose which server type to use (@CHOICES@ - default %default).",
+                   action = "store", dest = "servertype", default = "process")
+
+        parser.add_option("", "--token", help = "Specify the connection token to be used when connecting to a remote server.",
+                   action = "store", dest = "xmlrpctoken")
+
+        parser.add_option("", "--revisions-changed", help = "Set the exit code depending on whether upstream floating revisions have changed or not.",
+                   action = "store_true", dest = "revisions_changed", default = False)
+
+        parser.add_option("", "--server-only", help = "Run bitbake without a UI, only starting a server (cooker) process.",
+                   action = "store_true", dest = "server_only", default = False)
+
+        parser.add_option("-B", "--bind", help = "The name/address for the bitbake server to bind to.",
+                   action = "store", dest = "bind", default = False)
+
+        parser.add_option("", "--no-setscene", help = "Do not run any setscene tasks. sstate will be ignored and everything needed, built.",
+                   action = "store_true", dest = "nosetscene", default = False)
+
+        parser.add_option("", "--remote-server", help = "Connect to the specified server.",
+                   action = "store", dest = "remote_server", default = False)
+
+        parser.add_option("-m", "--kill-server", help = "Terminate the remote server.",
+                    action = "store_true", dest = "kill_server", default = False)
+
+        parser.add_option("", "--observe-only", help = "Connect to a server as an observing-only client.",
+                   action = "store_true", dest = "observe_only", default = False)
+
+        parser.add_option("", "--status-only", help = "Check the status of the remote bitbake server.",
+                   action = "store_true", dest = "status_only", default = False)
+
+        parser.add_option("-w", "--write-log", help = "Writes the event log of the build to a bitbake event json file. Use '' (empty string) to assign the name automatically.",
+                   action = "store", dest = "writeeventlog")
+
+        options, targets = parser.parse_args(argv)
+
+        # some environmental variables set also configuration options
+        if "BBSERVER" in os.environ:
+            options.servertype = "xmlrpc"
+            options.remote_server = os.environ["BBSERVER"]
+
+        if "BBTOKEN" in os.environ:
+            options.xmlrpctoken = os.environ["BBTOKEN"]
+
+        if "BBEVENTLOG" is os.environ:
+            options.writeeventlog = os.environ["BBEVENTLOG"]
+
+        # fill in proper log name if not supplied
+        if options.writeeventlog is not None and len(options.writeeventlog) == 0:
+            import datetime
+            options.writeeventlog = "bitbake_eventlog_%s.json" % datetime.datetime.now().strftime("%Y%m%d%H%M%S")
+
+        # if BBSERVER says to autodetect, let's do that
+        if options.remote_server:
+            [host, port] = options.remote_server.split(":", 2)
+            port = int(port)
+            # use automatic port if port set to -1, means read it from
+            # the bitbake.lock file; this is a bit tricky, but we always expect
+            # to be in the base of the build directory if we need to have a
+            # chance to start the server later, anyway
+            if port == -1:
+                lock_location = "./bitbake.lock"
+                # we try to read the address at all times; if the server is not started,
+                # we'll try to start it after the first connect fails, below
+                try:
+                    lf = open(lock_location, 'r')
+                    remotedef = lf.readline()
+                    [host, port] = remotedef.split(":")
+                    port = int(port)
+                    lf.close()
+                    options.remote_server = remotedef
+                except Exception as e:
+                    raise BBMainException("Failed to read bitbake.lock (%s), invalid port" % str(e))
+
+        return options, targets[1:]
+
+
+def start_server(servermodule, configParams, configuration, features):
+    server = servermodule.BitBakeServer()
+    if configParams.bind:
+        (host, port) = configParams.bind.split(':')
+        server.initServer((host, int(port)))
+        configuration.interface = [ server.serverImpl.host, server.serverImpl.port ]
+    else:
+        server.initServer()
+        configuration.interface = []
+
+    try:
+        configuration.setServerRegIdleCallback(server.getServerIdleCB())
+
+        cooker = bb.cooker.BBCooker(configuration, features)
+
+        server.addcooker(cooker)
+        server.saveConnectionDetails()
+    except Exception as e:
+        exc_info = sys.exc_info()
+        while hasattr(server, "event_queue"):
+            try:
+                import queue
+            except ImportError:
+                import Queue as queue
+            try:
+                event = server.event_queue.get(block=False)
+            except (queue.Empty, IOError):
+                break
+            if isinstance(event, logging.LogRecord):
+                logger.handle(event)
+        raise exc_info[1], None, exc_info[2]
+    server.detach()
+    cooker.lock.close()
+    return server
+
+
+def bitbake_main(configParams, configuration):
+
+    # Python multiprocessing requires /dev/shm on Linux
+    if sys.platform.startswith('linux') and not os.access('/dev/shm', os.W_OK | os.X_OK):
+        raise BBMainException("FATAL: /dev/shm does not exist or is not writable")
+
+    # Unbuffer stdout to avoid log truncation in the event
+    # of an unorderly exit as well as to provide timely
+    # updates to log files for use with tail
+    try:
+        if sys.stdout.name == '<stdout>':
+            sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
+    except:
+        pass
+
+
+    configuration.setConfigParameters(configParams)
+
+    ui_module = import_extension_module(bb.ui, configParams.ui, 'main')
+    servermodule = import_extension_module(bb.server, configParams.servertype, 'BitBakeServer')
+
+    if configParams.server_only:
+        if configParams.servertype != "xmlrpc":
+            raise BBMainException("FATAL: If '--server-only' is defined, we must set the "
+                                  "servertype as 'xmlrpc'.\n")
+        if not configParams.bind:
+            raise BBMainException("FATAL: The '--server-only' option requires a name/address "
+                                  "to bind to with the -B option.\n")
+        if configParams.remote_server:
+            raise BBMainException("FATAL: The '--server-only' option conflicts with %s.\n" %
+                                  ("the BBSERVER environment variable" if "BBSERVER" in os.environ \
+                                   else "the '--remote-server' option" ))
+
+    if configParams.bind and configParams.servertype != "xmlrpc":
+        raise BBMainException("FATAL: If '-B' or '--bind' is defined, we must "
+                              "set the servertype as 'xmlrpc'.\n")
+
+    if configParams.remote_server and configParams.servertype != "xmlrpc":
+        raise BBMainException("FATAL: If '--remote-server' is defined, we must "
+                              "set the servertype as 'xmlrpc'.\n")
+
+    if configParams.observe_only and (not configParams.remote_server or configParams.bind):
+        raise BBMainException("FATAL: '--observe-only' can only be used by UI clients "
+                              "connecting to a server.\n")
+
+    if configParams.kill_server and not configParams.remote_server:
+        raise BBMainException("FATAL: '--kill-server' can only be used to terminate a remote server")
+
+    if "BBDEBUG" in os.environ:
+        level = int(os.environ["BBDEBUG"])
+        if level > configuration.debug:
+            configuration.debug = level
+
+    bb.msg.init_msgconfig(configParams.verbose, configuration.debug,
+                         configuration.debug_domains)
+
+    # Ensure logging messages get sent to the UI as events
+    handler = bb.event.LogHandler()
+    if not configParams.status_only:
+        # In status only mode there are no logs and no UI
+        logger.addHandler(handler)
+
+    # Clear away any spurious environment variables while we stoke up the cooker
+    cleanedvars = bb.utils.clean_environment()
+
+    featureset = []
+    if not configParams.server_only:
+        # Collect the feature set for the UI
+        featureset = getattr(ui_module, "featureSet", [])
+
+    if not configParams.remote_server:
+        # we start a server with a given configuration
+        server = start_server(servermodule, configParams, configuration, featureset)
+        bb.event.ui_queue = []
+    else:
+        # we start a stub server that is actually a XMLRPClient that connects to a real server
+        server = servermodule.BitBakeXMLRPCClient(configParams.observe_only, configParams.xmlrpctoken)
+        server.saveConnectionDetails(configParams.remote_server)
+
+
+    if not configParams.server_only:
+        try:
+            server_connection = server.establishConnection(featureset)
+        except Exception as e:
+            bb.fatal("Could not connect to server %s: %s" % (configParams.remote_server, str(e)))
+
+        # Restore the environment in case the UI needs it
+        for k in cleanedvars:
+            os.environ[k] = cleanedvars[k]
+
+        logger.removeHandler(handler)
+
+
+        if configParams.status_only:
+            server_connection.terminate()
+            return 0
+
+        if configParams.kill_server:
+            server_connection.connection.terminateServer()
+            bb.event.ui_queue = []
+            return 0
+
+        try:
+            return ui_module.main(server_connection.connection, server_connection.events, configParams)
+        finally:
+            bb.event.ui_queue = []
+            server_connection.terminate()
+    else:
+        print("Bitbake server address: %s, server port: %s" % (server.serverImpl.host, server.serverImpl.port))
+        return 0
+
+    return 1
diff --git a/bitbake/lib/bb/methodpool.py b/bitbake/lib/bb/methodpool.py
new file mode 100644
index 0000000..bf2e9f5
--- /dev/null
+++ b/bitbake/lib/bb/methodpool.py
@@ -0,0 +1,29 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+#
+# Copyright (C)       2006 Holger Hans Peter Freyther
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from bb.utils import better_compile, better_exec
+
+def insert_method(modulename, code, fn):
+    """
+    Add code of a module should be added. The methods
+    will be simply added, no checking will be done
+    """
+    comp = better_compile(code, modulename, fn )
+    better_exec(comp, None, code, fn)
+
diff --git a/bitbake/lib/bb/monitordisk.py b/bitbake/lib/bb/monitordisk.py
new file mode 100644
index 0000000..466523c
--- /dev/null
+++ b/bitbake/lib/bb/monitordisk.py
@@ -0,0 +1,263 @@
+#!/usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Copyright (C) 2012 Robert Yang
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import os, logging, re, sys
+import bb
+logger = logging.getLogger("BitBake.Monitor")
+
+def printErr(info):
+    logger.error("%s\n       Disk space monitor will NOT be enabled" % info)
+
+def convertGMK(unit):
+
+    """ Convert the space unit G, M, K, the unit is case-insensitive """
+
+    unitG = re.match('([1-9][0-9]*)[gG]\s?$', unit)
+    if unitG:
+        return int(unitG.group(1)) * (1024 ** 3)
+    unitM = re.match('([1-9][0-9]*)[mM]\s?$', unit)
+    if unitM:
+        return int(unitM.group(1)) * (1024 ** 2)
+    unitK = re.match('([1-9][0-9]*)[kK]\s?$', unit)
+    if unitK:
+        return int(unitK.group(1)) * 1024
+    unitN = re.match('([1-9][0-9]*)\s?$', unit)
+    if unitN:
+        return int(unitN.group(1))
+    else:
+        return None
+
+def getMountedDev(path):
+
+    """ Get the device mounted at the path, uses /proc/mounts """
+
+    # Get the mount point of the filesystem containing path
+    # st_dev is the ID of device containing file
+    parentDev = os.stat(path).st_dev
+    currentDev = parentDev
+    # When the current directory's device is different from the
+    # parent's, then the current directory is a mount point
+    while parentDev == currentDev:
+        mountPoint = path
+        # Use dirname to get the parent's directory
+        path = os.path.dirname(path)
+        # Reach the "/"
+        if path == mountPoint:
+            break
+        parentDev= os.stat(path).st_dev
+
+    try:
+        with open("/proc/mounts", "r") as ifp:
+            for line in ifp:
+                procLines = line.rstrip('\n').split()
+                if procLines[1] == mountPoint:
+                    return procLines[0]
+    except EnvironmentError:
+        pass
+    return None
+
+def getDiskData(BBDirs, configuration):
+
+    """Prepare disk data for disk space monitor"""
+
+    # Save the device IDs, need the ID to be unique (the dictionary's key is
+    # unique), so that when more than one directory is located on the same
+    # device, we just monitor it once
+    devDict = {}
+    for pathSpaceInode in BBDirs.split():
+        # The input format is: "dir,space,inode", dir is a must, space
+        # and inode are optional
+        pathSpaceInodeRe = re.match('([^,]*),([^,]*),([^,]*),?(.*)', pathSpaceInode)
+        if not pathSpaceInodeRe:
+            printErr("Invalid value in BB_DISKMON_DIRS: %s" % pathSpaceInode)
+            return None
+
+        action = pathSpaceInodeRe.group(1)
+        if action not in ("ABORT", "STOPTASKS", "WARN"):
+            printErr("Unknown disk space monitor action: %s" % action)
+            return None
+
+        path = os.path.realpath(pathSpaceInodeRe.group(2))
+        if not path:
+            printErr("Invalid path value in BB_DISKMON_DIRS: %s" % pathSpaceInode)
+            return None
+
+        # The disk space or inode is optional, but it should have a correct
+        # value once it is specified
+        minSpace = pathSpaceInodeRe.group(3)
+        if minSpace:
+            minSpace = convertGMK(minSpace)
+            if not minSpace:
+                printErr("Invalid disk space value in BB_DISKMON_DIRS: %s" % pathSpaceInodeRe.group(3))
+                return None
+        else:
+            # None means that it is not specified
+            minSpace = None
+
+        minInode = pathSpaceInodeRe.group(4)
+        if minInode:
+            minInode = convertGMK(minInode)
+            if not minInode:
+                printErr("Invalid inode value in BB_DISKMON_DIRS: %s" % pathSpaceInodeRe.group(4))
+                return None
+        else:
+            # None means that it is not specified
+            minInode = None
+
+        if minSpace is None and minInode is None:
+            printErr("No disk space or inode value in found BB_DISKMON_DIRS: %s" % pathSpaceInode)
+            return None
+        # mkdir for the directory since it may not exist, for example the
+        # DL_DIR may not exist at the very beginning
+        if not os.path.exists(path):
+            bb.utils.mkdirhier(path)
+        dev = getMountedDev(path)
+        # Use path/action as the key
+        devDict[os.path.join(path, action)] = [dev, minSpace, minInode]
+
+    return devDict
+
+def getInterval(configuration):
+
+    """ Get the disk space interval """
+
+    # The default value is 50M and 5K.
+    spaceDefault = 50 * 1024 * 1024
+    inodeDefault = 5 * 1024
+
+    interval = configuration.getVar("BB_DISKMON_WARNINTERVAL", True)
+    if not interval:
+        return spaceDefault, inodeDefault
+    else:
+        # The disk space or inode interval is optional, but it should
+        # have a correct value once it is specified
+        intervalRe = re.match('([^,]*),?\s*(.*)', interval)
+        if intervalRe:
+            intervalSpace = intervalRe.group(1)
+            if intervalSpace:
+                intervalSpace = convertGMK(intervalSpace)
+                if not intervalSpace:
+                    printErr("Invalid disk space interval value in BB_DISKMON_WARNINTERVAL: %s" % intervalRe.group(1))
+                    return None, None
+            else:
+                intervalSpace = spaceDefault
+            intervalInode = intervalRe.group(2)
+            if intervalInode:
+                intervalInode = convertGMK(intervalInode)
+                if not intervalInode:
+                    printErr("Invalid disk inode interval value in BB_DISKMON_WARNINTERVAL: %s" % intervalRe.group(2))
+                    return None, None
+            else:
+                intervalInode = inodeDefault
+            return intervalSpace, intervalInode
+        else:
+            printErr("Invalid interval value in BB_DISKMON_WARNINTERVAL: %s" % interval)
+            return None, None
+
+class diskMonitor:
+
+    """Prepare the disk space monitor data"""
+
+    def __init__(self, configuration):
+
+        self.enableMonitor = False
+        self.configuration = configuration
+
+        BBDirs = configuration.getVar("BB_DISKMON_DIRS", True) or None
+        if BBDirs:
+            self.devDict = getDiskData(BBDirs, configuration)
+            if self.devDict:
+                self.spaceInterval, self.inodeInterval = getInterval(configuration)
+                if self.spaceInterval and self.inodeInterval:
+                    self.enableMonitor = True
+                    # These are for saving the previous disk free space and inode, we
+                    # use them to avoid printing too many warning messages
+                    self.preFreeS = {}
+                    self.preFreeI = {}
+                    # This is for STOPTASKS and ABORT, to avoid printing the message
+                    # repeatedly while waiting for the tasks to finish
+                    self.checked = {}
+                    for k in self.devDict:
+                        self.preFreeS[k] = 0
+                        self.preFreeI[k] = 0
+                        self.checked[k] = False
+                    if self.spaceInterval is None and self.inodeInterval is None:
+                        self.enableMonitor = False
+
+    def check(self, rq):
+
+        """ Take action for the monitor """
+
+        if self.enableMonitor:
+            for k in self.devDict:
+                path = os.path.dirname(k)
+                action = os.path.basename(k)
+                dev = self.devDict[k][0]
+                minSpace = self.devDict[k][1]
+                minInode = self.devDict[k][2]
+
+                st = os.statvfs(path)
+
+                # The free space, float point number
+                freeSpace = st.f_bavail * st.f_frsize
+
+                if minSpace and freeSpace < minSpace:
+                    # Always show warning, the self.checked would always be False if the action is WARN
+                    if self.preFreeS[k] == 0 or self.preFreeS[k] - freeSpace > self.spaceInterval and not self.checked[k]:
+                        logger.warn("The free space of %s (%s) is running low (%.3fGB left)" % \
+                                (path, dev, freeSpace / 1024 / 1024 / 1024.0))
+                        self.preFreeS[k] = freeSpace
+
+                    if action == "STOPTASKS" and not self.checked[k]:
+                        logger.error("No new tasks can be executed since the disk space monitor action is \"STOPTASKS\"!")
+                        self.checked[k] = True
+                        rq.finish_runqueue(False)
+                        bb.event.fire(bb.event.DiskFull(dev, 'disk', freeSpace, path), self.configuration)
+                    elif action == "ABORT" and not self.checked[k]:
+                        logger.error("Immediately abort since the disk space monitor action is \"ABORT\"!")
+                        self.checked[k] = True
+                        rq.finish_runqueue(True)
+                        bb.event.fire(bb.event.DiskFull(dev, 'disk', freeSpace, path), self.configuration)
+
+                # The free inodes, float point number
+                freeInode = st.f_favail
+
+                if minInode and freeInode < minInode:
+                    # Some filesystems use dynamic inodes so can't run out
+                    # (e.g. btrfs). This is reported by the inode count being 0.
+                    if st.f_files == 0:
+                        self.devDict[k][2] = None
+                        continue
+                    # Always show warning, the self.checked would always be False if the action is WARN
+                    if self.preFreeI[k] == 0 or self.preFreeI[k] - freeInode > self.inodeInterval and not self.checked[k]:
+                        logger.warn("The free inode of %s (%s) is running low (%.3fK left)" % \
+                                (path, dev, freeInode / 1024.0))
+                        self.preFreeI[k] = freeInode
+
+                    if action  == "STOPTASKS" and not self.checked[k]:
+                        logger.error("No new tasks can be executed since the disk space monitor action is \"STOPTASKS\"!")
+                        self.checked[k] = True
+                        rq.finish_runqueue(False)
+                        bb.event.fire(bb.event.DiskFull(dev, 'inode', freeInode, path), self.configuration)
+                    elif action  == "ABORT" and not self.checked[k]:
+                        logger.error("Immediately abort since the disk space monitor action is \"ABORT\"!")
+                        self.checked[k] = True
+                        rq.finish_runqueue(True)
+                        bb.event.fire(bb.event.DiskFull(dev, 'inode', freeInode, path), self.configuration)
+        return
diff --git a/bitbake/lib/bb/msg.py b/bitbake/lib/bb/msg.py
new file mode 100644
index 0000000..786b5ae
--- /dev/null
+++ b/bitbake/lib/bb/msg.py
@@ -0,0 +1,199 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake 'msg' implementation
+
+Message handling infrastructure for bitbake
+
+"""
+
+# Copyright (C) 2006        Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import sys
+import copy
+import logging
+import collections
+from itertools import groupby
+import warnings
+import bb
+import bb.event
+
+class BBLogFormatter(logging.Formatter):
+    """Formatter which ensures that our 'plain' messages (logging.INFO + 1) are used as is"""
+
+    DEBUG3 = logging.DEBUG - 2
+    DEBUG2 = logging.DEBUG - 1
+    DEBUG = logging.DEBUG
+    VERBOSE = logging.INFO - 1
+    NOTE = logging.INFO
+    PLAIN = logging.INFO + 1
+    ERROR = logging.ERROR
+    WARNING = logging.WARNING
+    CRITICAL = logging.CRITICAL
+
+    levelnames = {
+        DEBUG3   : 'DEBUG',
+        DEBUG2   : 'DEBUG',
+        DEBUG   : 'DEBUG',
+        VERBOSE: 'NOTE',
+        NOTE    : 'NOTE',
+        PLAIN  : '',
+        WARNING : 'WARNING',
+        ERROR   : 'ERROR',
+        CRITICAL: 'ERROR',
+    }
+
+    color_enabled = False
+    BASECOLOR, BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(29,38)
+
+    COLORS = {
+        DEBUG3  : CYAN,
+        DEBUG2  : CYAN,
+        DEBUG   : CYAN,
+        VERBOSE : BASECOLOR,
+        NOTE    : BASECOLOR,
+        PLAIN   : BASECOLOR,
+        WARNING : YELLOW,
+        ERROR   : RED,
+        CRITICAL: RED,
+    }
+
+    BLD = '\033[1;%dm'
+    STD = '\033[%dm'
+    RST = '\033[0m'
+
+    def getLevelName(self, levelno):
+        try:
+            return self.levelnames[levelno]
+        except KeyError:
+            self.levelnames[levelno] = value = 'Level %d' % levelno
+            return value
+
+    def format(self, record):
+        record.levelname = self.getLevelName(record.levelno)
+        if record.levelno == self.PLAIN:
+            msg = record.getMessage()
+        else:
+            if self.color_enabled:
+                record = self.colorize(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
+
+    def colorize(self, record):
+        color = self.COLORS[record.levelno]
+        if self.color_enabled and color is not None:
+            record = copy.copy(record)
+            record.levelname = "".join([self.BLD % color, record.levelname, self.RST])
+            record.msg = "".join([self.STD % color, record.msg, self.RST])
+        return record
+
+    def enable_color(self):
+        self.color_enabled = True
+
+class BBLogFilter(object):
+    def __init__(self, handler, level, debug_domains):
+        self.stdlevel = level
+        self.debug_domains = debug_domains
+        loglevel = level
+        for domain in debug_domains:
+            if debug_domains[domain] < loglevel:
+                loglevel = debug_domains[domain]
+        handler.setLevel(loglevel)
+        handler.addFilter(self)
+
+    def filter(self, record):
+        if record.levelno >= self.stdlevel:
+            return True
+        if record.name in self.debug_domains and record.levelno >= self.debug_domains[record.name]:
+            return True
+        return False
+
+class BBLogFilterStdErr(BBLogFilter):
+    def filter(self, record):
+        if not BBLogFilter.filter(self, record):
+            return False
+        if record.levelno >= logging.ERROR:
+            return True
+        return False
+
+class BBLogFilterStdOut(BBLogFilter):
+    def filter(self, record):
+        if not BBLogFilter.filter(self, record):
+            return False
+        if record.levelno < logging.ERROR:
+            return True
+        return False
+
+# Message control functions
+#
+
+loggerDefaultDebugLevel = 0
+loggerDefaultVerbose = False
+loggerVerboseLogs = False
+loggerDefaultDomains = []
+
+def init_msgconfig(verbose, debug, debug_domains=None):
+    """
+    Set default verbosity and debug levels config the logger
+    """
+    bb.msg.loggerDefaultDebugLevel = debug
+    bb.msg.loggerDefaultVerbose = verbose
+    if verbose:
+        bb.msg.loggerVerboseLogs = True
+    if debug_domains:
+        bb.msg.loggerDefaultDomains = debug_domains
+    else:
+        bb.msg.loggerDefaultDomains = []
+
+def constructLogOptions():
+    debug = loggerDefaultDebugLevel
+    verbose = loggerDefaultVerbose
+    domains = loggerDefaultDomains
+
+    if debug:
+        level = BBLogFormatter.DEBUG - debug + 1
+    elif verbose:
+        level = BBLogFormatter.VERBOSE
+    else:
+        level = BBLogFormatter.NOTE
+
+    debug_domains = {}
+    for (domainarg, iterator) in groupby(domains):
+        dlevel = len(tuple(iterator))
+        debug_domains["BitBake.%s" % domainarg] = logging.DEBUG - dlevel + 1
+    return level, debug_domains
+
+def addDefaultlogFilter(handler, cls = BBLogFilter):
+    level, debug_domains = constructLogOptions()
+
+    cls(handler, level, debug_domains)
+
+#
+# Message handling functions
+#
+
+def fatal(msgdomain, msg):
+    if msgdomain:
+        logger = logging.getLogger("BitBake.%s" % msgdomain)
+    else:
+        logger = logging.getLogger("BitBake")
+    logger.critical(msg)
+    sys.exit(1)
diff --git a/bitbake/lib/bb/namedtuple_with_abc.py b/bitbake/lib/bb/namedtuple_with_abc.py
new file mode 100644
index 0000000..32f2fc6
--- /dev/null
+++ b/bitbake/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
+
+    # Note that behavior: the standard namedtuple methods cannot be
+    # overridden 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)
diff --git a/bitbake/lib/bb/parse/__init__.py b/bitbake/lib/bb/parse/__init__.py
new file mode 100644
index 0000000..67ec71f
--- /dev/null
+++ b/bitbake/lib/bb/parse/__init__.py
@@ -0,0 +1,170 @@
+"""
+BitBake Parsers
+
+File parsers for the BitBake build tools.
+
+"""
+
+
+# Copyright (C) 2003, 2004  Chris Larson
+# Copyright (C) 2003, 2004  Phil Blundell
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Based on functions from the base bb module, Copyright 2003 Holger Schurig
+
+handlers = []
+
+import errno
+import logging
+import os
+import stat
+import bb
+import bb.utils
+import bb.siggen
+
+logger = logging.getLogger("BitBake.Parsing")
+
+class ParseError(Exception):
+    """Exception raised when parsing fails"""
+    def __init__(self, msg, filename, lineno=0):
+        self.msg = msg
+        self.filename = filename
+        self.lineno = lineno
+        Exception.__init__(self, msg, filename, lineno)
+
+    def __str__(self):
+        if self.lineno:
+            return "ParseError at %s:%d: %s" % (self.filename, self.lineno, self.msg)
+        else:
+            return "ParseError in %s: %s" % (self.filename, self.msg)
+
+class SkipRecipe(Exception):
+    """Exception raised to skip this recipe"""
+
+class SkipPackage(SkipRecipe):
+    """Exception raised to skip this recipe (use SkipRecipe in new code)"""
+
+__mtime_cache = {}
+def cached_mtime(f):
+    if f not in __mtime_cache:
+        __mtime_cache[f] = os.stat(f)[stat.ST_MTIME]
+    return __mtime_cache[f]
+
+def cached_mtime_noerror(f):
+    if f not in __mtime_cache:
+        try:
+            __mtime_cache[f] = os.stat(f)[stat.ST_MTIME]
+        except OSError:
+            return 0
+    return __mtime_cache[f]
+
+def update_mtime(f):
+    try:
+        __mtime_cache[f] = os.stat(f)[stat.ST_MTIME]
+    except OSError:
+        if f in __mtime_cache:
+            del __mtime_cache[f]
+        return 0
+    return __mtime_cache[f]
+
+def update_cache(f):
+    if f in __mtime_cache:
+        logger.debug(1, "Updating mtime cache for %s" % f)
+        update_mtime(f)
+
+def mark_dependency(d, f):
+    if f.startswith('./'):
+        f = "%s/%s" % (os.getcwd(), f[2:])
+    deps = (d.getVar('__depends', False) or [])
+    s = (f, cached_mtime_noerror(f))
+    if s not in deps:
+        deps.append(s)
+        d.setVar('__depends', deps)
+
+def check_dependency(d, f):
+    s = (f, cached_mtime_noerror(f))
+    deps = (d.getVar('__depends', False) or [])
+    return s in deps
+   
+def supports(fn, data):
+    """Returns true if we have a handler for this file, false otherwise"""
+    for h in handlers:
+        if h['supports'](fn, data):
+            return 1
+    return 0
+
+def handle(fn, data, include = 0):
+    """Call the handler that is appropriate for this file"""
+    for h in handlers:
+        if h['supports'](fn, data):
+            with data.inchistory.include(fn):
+                return h['handle'](fn, data, include)
+    raise ParseError("not a BitBake file", fn)
+
+def init(fn, data):
+    for h in handlers:
+        if h['supports'](fn):
+            return h['init'](data)
+
+def init_parser(d):
+    bb.parse.siggen = bb.siggen.init(d)
+
+def resolve_file(fn, d):
+    if not os.path.isabs(fn):
+        bbpath = d.getVar("BBPATH", True)
+        newfn, attempts = bb.utils.which(bbpath, fn, history=True)
+        for af in attempts:
+            mark_dependency(d, af)
+        if not newfn:
+            raise IOError(errno.ENOENT, "file %s not found in %s" % (fn, bbpath))
+        fn = newfn
+
+    mark_dependency(d, fn)
+    if not os.path.isfile(fn):
+        raise IOError(errno.ENOENT, "file %s not found" % fn)
+
+    return fn
+
+# Used by OpenEmbedded metadata
+__pkgsplit_cache__={}
+def vars_from_file(mypkg, d):
+    if not mypkg or not mypkg.endswith((".bb", ".bbappend")):
+        return (None, None, None)
+    if mypkg in __pkgsplit_cache__:
+        return __pkgsplit_cache__[mypkg]
+
+    myfile = os.path.splitext(os.path.basename(mypkg))
+    parts = myfile[0].split('_')
+    __pkgsplit_cache__[mypkg] = parts
+    if len(parts) > 3:
+        raise ParseError("Unable to generate default variables from filename (too many underscores)", mypkg)
+    exp = 3 - len(parts)
+    tmplist = []
+    while exp != 0:
+        exp -= 1
+        tmplist.append(None)
+    parts.extend(tmplist)
+    return parts
+
+def get_file_depends(d):
+    '''Return the dependent files'''
+    dep_files = []
+    depends = d.getVar('__base_depends', True) or []
+    depends = depends + (d.getVar('__depends', True) or [])
+    for (fn, _) in depends:
+        dep_files.append(os.path.abspath(fn))
+    return " ".join(dep_files)
+
+from bb.parse.parse_py import __version__, ConfHandler, BBHandler
diff --git a/bitbake/lib/bb/parse/ast.py b/bitbake/lib/bb/parse/ast.py
new file mode 100644
index 0000000..11db180
--- /dev/null
+++ b/bitbake/lib/bb/parse/ast.py
@@ -0,0 +1,481 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+ AbstractSyntaxTree classes for the Bitbake language
+"""
+
+# Copyright (C) 2003, 2004 Chris Larson
+# Copyright (C) 2003, 2004 Phil Blundell
+# Copyright (C) 2009 Holger Hans Peter Freyther
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from __future__ import absolute_import
+from future_builtins import filter
+import re
+import string
+import logging
+import bb
+import itertools
+from bb import methodpool
+from bb.parse import logger
+
+_bbversions_re = re.compile(r"\[(?P<from>[0-9]+)-(?P<to>[0-9]+)\]")
+
+class StatementGroup(list):
+    def eval(self, data):
+        for statement in self:
+            statement.eval(data)
+
+class AstNode(object):
+    def __init__(self, filename, lineno):
+        self.filename = filename
+        self.lineno = lineno
+
+class IncludeNode(AstNode):
+    def __init__(self, filename, lineno, what_file, force):
+        AstNode.__init__(self, filename, lineno)
+        self.what_file = what_file
+        self.force = force
+
+    def eval(self, data):
+        """
+        Include the file and evaluate the statements
+        """
+        s = data.expand(self.what_file)
+        logger.debug(2, "CONF %s:%s: including %s", self.filename, self.lineno, s)
+
+        # TODO: Cache those includes... maybe not here though
+        if self.force:
+            bb.parse.ConfHandler.include(self.filename, s, self.lineno, data, "include required")
+        else:
+            bb.parse.ConfHandler.include(self.filename, s, self.lineno, data, False)
+
+class ExportNode(AstNode):
+    def __init__(self, filename, lineno, var):
+        AstNode.__init__(self, filename, lineno)
+        self.var = var
+
+    def eval(self, data):
+        data.setVarFlag(self.var, "export", 1, op = 'exported')
+
+class DataNode(AstNode):
+    """
+    Various data related updates. For the sake of sanity
+    we have one class doing all this. This means that all
+    this need to be re-evaluated... we might be able to do
+    that faster with multiple classes.
+    """
+    def __init__(self, filename, lineno, groupd):
+        AstNode.__init__(self, filename, lineno)
+        self.groupd = groupd
+
+    def getFunc(self, key, data):
+        if 'flag' in self.groupd and self.groupd['flag'] != None:
+            return data.getVarFlag(key, self.groupd['flag'], noweakdefault=True)
+        else:
+            return data.getVar(key, False, noweakdefault=True, parsing=True)
+
+    def eval(self, data):
+        groupd = self.groupd
+        key = groupd["var"]
+        loginfo = {
+            'variable': key,
+            'file': self.filename,
+            'line': self.lineno,
+        }
+        if "exp" in groupd and groupd["exp"] != None:
+            data.setVarFlag(key, "export", 1, op = 'exported', **loginfo)
+
+        op = "set"
+        if "ques" in groupd and groupd["ques"] != None:
+            val = self.getFunc(key, data)
+            op = "set?"
+            if val == None:
+                val = groupd["value"]
+        elif "colon" in groupd and groupd["colon"] != None:
+            e = data.createCopy()
+            bb.data.update_data(e)
+            op = "immediate"
+            val = e.expand(groupd["value"], key + "[:=]")
+        elif "append" in groupd and groupd["append"] != None:
+            op = "append"
+            val = "%s %s" % ((self.getFunc(key, data) or ""), groupd["value"])
+        elif "prepend" in groupd and groupd["prepend"] != None:
+            op = "prepend"
+            val = "%s %s" % (groupd["value"], (self.getFunc(key, data) or ""))
+        elif "postdot" in groupd and groupd["postdot"] != None:
+            op = "postdot"
+            val = "%s%s" % ((self.getFunc(key, data) or ""), groupd["value"])
+        elif "predot" in groupd and groupd["predot"] != None:
+            op = "predot"
+            val = "%s%s" % (groupd["value"], (self.getFunc(key, data) or ""))
+        else:
+            val = groupd["value"]
+
+        flag = None
+        if 'flag' in groupd and groupd['flag'] != None:
+            flag = groupd['flag']
+        elif groupd["lazyques"]:
+            flag = "_defaultval"
+
+        loginfo['op'] = op
+        loginfo['detail'] = groupd["value"]
+
+        if flag:
+            data.setVarFlag(key, flag, val, **loginfo)
+        else:
+            data.setVar(key, val, parsing=True, **loginfo)
+
+class MethodNode(AstNode):
+    tr_tbl = string.maketrans('/.+-@%&', '_______')
+
+    def __init__(self, filename, lineno, func_name, body):
+        AstNode.__init__(self, filename, lineno)
+        self.func_name = func_name
+        self.body = body
+
+    def eval(self, data):
+        text = '\n'.join(self.body)
+        if self.func_name == "__anonymous":
+            funcname = ("__anon_%s_%s" % (self.lineno, self.filename.translate(MethodNode.tr_tbl)))
+            text = "def %s(d):\n" % (funcname) + text
+            bb.methodpool.insert_method(funcname, text, self.filename)
+            anonfuncs = data.getVar('__BBANONFUNCS', False) or []
+            anonfuncs.append(funcname)
+            data.setVar('__BBANONFUNCS', anonfuncs)
+            data.setVar(funcname, text, parsing=True)
+        else:
+            data.setVarFlag(self.func_name, "func", 1)
+            data.setVar(self.func_name, text, parsing=True)
+
+class PythonMethodNode(AstNode):
+    def __init__(self, filename, lineno, function, modulename, body):
+        AstNode.__init__(self, filename, lineno)
+        self.function = function
+        self.modulename = modulename
+        self.body = body
+
+    def eval(self, data):
+        # Note we will add root to parsedmethods after having parse
+        # 'this' file. This means we will not parse methods from
+        # bb classes twice
+        text = '\n'.join(self.body)
+        bb.methodpool.insert_method(self.modulename, text, self.filename)
+        data.setVarFlag(self.function, "func", 1)
+        data.setVarFlag(self.function, "python", 1)
+        data.setVar(self.function, text, parsing=True)
+
+class MethodFlagsNode(AstNode):
+    def __init__(self, filename, lineno, key, m):
+        AstNode.__init__(self, filename, lineno)
+        self.key = key
+        self.m = m
+
+    def eval(self, data):
+        if data.getVar(self.key, False):
+            # clean up old version of this piece of metadata, as its
+            # flags could cause problems
+            data.setVarFlag(self.key, 'python', None)
+            data.setVarFlag(self.key, 'fakeroot', None)
+        if self.m.group("py") is not None:
+            data.setVarFlag(self.key, "python", "1")
+        else:
+            data.delVarFlag(self.key, "python")
+        if self.m.group("fr") is not None:
+            data.setVarFlag(self.key, "fakeroot", "1")
+        else:
+            data.delVarFlag(self.key, "fakeroot")
+
+class ExportFuncsNode(AstNode):
+    def __init__(self, filename, lineno, fns, classname):
+        AstNode.__init__(self, filename, lineno)
+        self.n = fns.split()
+        self.classname = classname
+
+    def eval(self, data):
+
+        for func in self.n:
+            calledfunc = self.classname + "_" + func
+
+            if data.getVar(func, False) and not data.getVarFlag(func, 'export_func'):
+                continue
+
+            if data.getVar(func, False):
+                data.setVarFlag(func, 'python', None)
+                data.setVarFlag(func, 'func', None)
+
+            for flag in [ "func", "python" ]:
+                if data.getVarFlag(calledfunc, flag):
+                    data.setVarFlag(func, flag, data.getVarFlag(calledfunc, flag))
+            for flag in [ "dirs" ]:
+                if data.getVarFlag(func, flag):
+                    data.setVarFlag(calledfunc, flag, data.getVarFlag(func, flag))
+
+            if data.getVarFlag(calledfunc, "python"):
+                data.setVar(func, "    bb.build.exec_func('" + calledfunc + "', d)\n", parsing=True)
+            else:
+                if "-" in self.classname:
+                   bb.fatal("The classname %s contains a dash character and is calling an sh function %s using EXPORT_FUNCTIONS. Since a dash is illegal in sh function names, this cannot work, please rename the class or don't use EXPORT_FUNCTIONS." % (self.classname, calledfunc))
+                data.setVar(func, "    " + calledfunc + "\n", parsing=True)
+            data.setVarFlag(func, 'export_func', '1')
+
+class AddTaskNode(AstNode):
+    def __init__(self, filename, lineno, func, before, after):
+        AstNode.__init__(self, filename, lineno)
+        self.func = func
+        self.before = before
+        self.after = after
+
+    def eval(self, data):
+        bb.build.addtask(self.func, self.before, self.after, data)
+
+class DelTaskNode(AstNode):
+    def __init__(self, filename, lineno, func):
+        AstNode.__init__(self, filename, lineno)
+        self.func = func
+
+    def eval(self, data):
+        bb.build.deltask(self.func, data)
+
+class BBHandlerNode(AstNode):
+    def __init__(self, filename, lineno, fns):
+        AstNode.__init__(self, filename, lineno)
+        self.hs = fns.split()
+
+    def eval(self, data):
+        bbhands = data.getVar('__BBHANDLERS', False) or []
+        for h in self.hs:
+            bbhands.append(h)
+            data.setVarFlag(h, "handler", 1)
+        data.setVar('__BBHANDLERS', bbhands)
+
+class InheritNode(AstNode):
+    def __init__(self, filename, lineno, classes):
+        AstNode.__init__(self, filename, lineno)
+        self.classes = classes
+
+    def eval(self, data):
+        bb.parse.BBHandler.inherit(self.classes, self.filename, self.lineno, data)
+
+def handleInclude(statements, filename, lineno, m, force):
+    statements.append(IncludeNode(filename, lineno, m.group(1), force))
+
+def handleExport(statements, filename, lineno, m):
+    statements.append(ExportNode(filename, lineno, m.group(1)))
+
+def handleData(statements, filename, lineno, groupd):
+    statements.append(DataNode(filename, lineno, groupd))
+
+def handleMethod(statements, filename, lineno, func_name, body):
+    statements.append(MethodNode(filename, lineno, func_name, body))
+
+def handlePythonMethod(statements, filename, lineno, funcname, modulename, body):
+    statements.append(PythonMethodNode(filename, lineno, funcname, modulename, body))
+
+def handleMethodFlags(statements, filename, lineno, key, m):
+    statements.append(MethodFlagsNode(filename, lineno, key, m))
+
+def handleExportFuncs(statements, filename, lineno, m, classname):
+    statements.append(ExportFuncsNode(filename, lineno, m.group(1), classname))
+
+def handleAddTask(statements, filename, lineno, m):
+    func = m.group("func")
+    before = m.group("before")
+    after = m.group("after")
+    if func is None:
+        return
+
+    statements.append(AddTaskNode(filename, lineno, func, before, after))
+
+def handleDelTask(statements, filename, lineno, m):
+    func = m.group("func")
+    if func is None:
+        return
+
+    statements.append(DelTaskNode(filename, lineno, func))
+
+def handleBBHandlers(statements, filename, lineno, m):
+    statements.append(BBHandlerNode(filename, lineno, m.group(1)))
+
+def handleInherit(statements, filename, lineno, m):
+    classes = m.group(1)
+    statements.append(InheritNode(filename, lineno, classes))
+
+def finalize(fn, d, variant = None):
+    all_handlers = {}
+    for var in d.getVar('__BBHANDLERS', False) or []:
+        # try to add the handler
+        bb.event.register(var, d.getVar(var, False), (d.getVarFlag(var, "eventmask", True) or "").split())
+
+    bb.event.fire(bb.event.RecipePreFinalise(fn), d)
+
+    bb.data.expandKeys(d)
+    bb.data.update_data(d)
+    code = []
+    for funcname in d.getVar("__BBANONFUNCS", False) or []:
+        code.append("%s(d)" % funcname)
+    bb.utils.better_exec("\n".join(code), {"d": d})
+    bb.data.update_data(d)
+
+    tasklist = d.getVar('__BBTASKS', False) or []
+    bb.build.add_tasks(tasklist, d)
+
+    bb.parse.siggen.finalise(fn, d, variant)
+
+    d.setVar('BBINCLUDED', bb.parse.get_file_depends(d))
+
+    bb.event.fire(bb.event.RecipeParsed(fn), d)
+
+def _create_variants(datastores, names, function, onlyfinalise):
+    def create_variant(name, orig_d, arg = None):
+        if onlyfinalise and name not in onlyfinalise:
+            return
+        new_d = bb.data.createCopy(orig_d)
+        function(arg or name, new_d)
+        datastores[name] = new_d
+
+    for variant, variant_d in datastores.items():
+        for name in names:
+            if not variant:
+                # Based on main recipe
+                create_variant(name, variant_d)
+            else:
+                create_variant("%s-%s" % (variant, name), variant_d, name)
+
+def _expand_versions(versions):
+    def expand_one(version, start, end):
+        for i in xrange(start, end + 1):
+            ver = _bbversions_re.sub(str(i), version, 1)
+            yield ver
+
+    versions = iter(versions)
+    while True:
+        try:
+            version = next(versions)
+        except StopIteration:
+            break
+
+        range_ver = _bbversions_re.search(version)
+        if not range_ver:
+            yield version
+        else:
+            newversions = expand_one(version, int(range_ver.group("from")),
+                                     int(range_ver.group("to")))
+            versions = itertools.chain(newversions, versions)
+
+def multi_finalize(fn, d):
+    appends = (d.getVar("__BBAPPEND", True) or "").split()
+    for append in appends:
+        logger.debug(1, "Appending .bbappend file %s to %s", append, fn)
+        bb.parse.BBHandler.handle(append, d, True)
+
+    onlyfinalise = d.getVar("__ONLYFINALISE", False)
+
+    safe_d = d
+    d = bb.data.createCopy(safe_d)
+    try:
+        finalize(fn, d)
+    except bb.parse.SkipRecipe as e:
+        d.setVar("__SKIPPED", e.args[0])
+    datastores = {"": safe_d}
+
+    versions = (d.getVar("BBVERSIONS", True) or "").split()
+    if versions:
+        pv = orig_pv = d.getVar("PV", True)
+        baseversions = {}
+
+        def verfunc(ver, d, pv_d = None):
+            if pv_d is None:
+                pv_d = d
+
+            overrides = d.getVar("OVERRIDES", True).split(":")
+            pv_d.setVar("PV", ver)
+            overrides.append(ver)
+            bpv = baseversions.get(ver) or orig_pv
+            pv_d.setVar("BPV", bpv)
+            overrides.append(bpv)
+            d.setVar("OVERRIDES", ":".join(overrides))
+
+        versions = list(_expand_versions(versions))
+        for pos, version in enumerate(list(versions)):
+            try:
+                pv, bpv = version.split(":", 2)
+            except ValueError:
+                pass
+            else:
+                versions[pos] = pv
+                baseversions[pv] = bpv
+
+        if pv in versions and not baseversions.get(pv):
+            versions.remove(pv)
+        else:
+            pv = versions.pop()
+
+            # This is necessary because our existing main datastore
+            # has already been finalized with the old PV, we need one
+            # that's been finalized with the new PV.
+            d = bb.data.createCopy(safe_d)
+            verfunc(pv, d, safe_d)
+            try:
+                finalize(fn, d)
+            except bb.parse.SkipRecipe as e:
+                d.setVar("__SKIPPED", e.args[0])
+
+        _create_variants(datastores, versions, verfunc, onlyfinalise)
+
+    extended = d.getVar("BBCLASSEXTEND", True) or ""
+    if extended:
+        # the following is to support bbextends with arguments, for e.g. multilib
+        # an example is as follows:
+        #   BBCLASSEXTEND = "multilib:lib32"
+        # it will create foo-lib32, inheriting multilib.bbclass and set
+        # BBEXTENDCURR to "multilib" and BBEXTENDVARIANT to "lib32"
+        extendedmap = {}
+        variantmap = {}
+
+        for ext in extended.split():
+            eext = ext.split(':', 2)
+            if len(eext) > 1:
+                extendedmap[ext] = eext[0]
+                variantmap[ext] = eext[1]
+            else:
+                extendedmap[ext] = ext
+
+        pn = d.getVar("PN", True)
+        def extendfunc(name, d):
+            if name != extendedmap[name]:
+                d.setVar("BBEXTENDCURR", extendedmap[name])
+                d.setVar("BBEXTENDVARIANT", variantmap[name])
+            else:
+                d.setVar("PN", "%s-%s" % (pn, name))
+            bb.parse.BBHandler.inherit(extendedmap[name], fn, 0, d)
+
+        safe_d.setVar("BBCLASSEXTEND", extended)
+        _create_variants(datastores, extendedmap.keys(), extendfunc, onlyfinalise)
+
+    for variant, variant_d in datastores.iteritems():
+        if variant:
+            try:
+                if not onlyfinalise or variant in onlyfinalise:
+                    finalize(fn, variant_d, variant)
+            except bb.parse.SkipRecipe as e:
+                variant_d.setVar("__SKIPPED", e.args[0])
+
+    if len(datastores) > 1:
+        variants = filter(None, datastores.iterkeys())
+        safe_d.setVar("__VARIANTS", " ".join(variants))
+
+    datastores[""] = d
+    return datastores
diff --git a/bitbake/lib/bb/parse/parse_py/BBHandler.py b/bitbake/lib/bb/parse/parse_py/BBHandler.py
new file mode 100644
index 0000000..ec097ba
--- /dev/null
+++ b/bitbake/lib/bb/parse/parse_py/BBHandler.py
@@ -0,0 +1,265 @@
+#!/usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+   class for handling .bb files
+
+   Reads a .bb file and obtains its metadata
+
+"""
+
+
+#  Copyright (C) 2003, 2004  Chris Larson
+#  Copyright (C) 2003, 2004  Phil Blundell
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from __future__ import absolute_import
+import re, bb, os
+import logging
+import bb.build, bb.utils
+from bb import data
+
+from . import ConfHandler
+from .. import resolve_file, ast, logger, ParseError
+from .ConfHandler import include, init
+
+# For compatibility
+bb.deprecate_import(__name__, "bb.parse", ["vars_from_file"])
+
+__func_start_regexp__    = re.compile( r"(((?P<py>python)|(?P<fr>fakeroot))\s*)*(?P<func>[\w\.\-\+\{\}\$]+)?\s*\(\s*\)\s*{$" )
+__inherit_regexp__       = re.compile( r"inherit\s+(.+)" )
+__export_func_regexp__   = re.compile( r"EXPORT_FUNCTIONS\s+(.+)" )
+__addtask_regexp__       = re.compile("addtask\s+(?P<func>\w+)\s*((before\s*(?P<before>((.*(?=after))|(.*))))|(after\s*(?P<after>((.*(?=before))|(.*)))))*")
+__deltask_regexp__       = re.compile("deltask\s+(?P<func>\w+)")
+__addhandler_regexp__    = re.compile( r"addhandler\s+(.+)" )
+__def_regexp__           = re.compile( r"def\s+(\w+).*:" )
+__python_func_regexp__   = re.compile( r"(\s+.*)|(^$)" )
+
+
+__infunc__ = []
+__inpython__ = False
+__body__   = []
+__classname__ = ""
+
+cached_statements = {}
+
+# We need to indicate EOF to the feeder. This code is so messy that
+# factoring it out to a close_parse_file method is out of question.
+# We will use the IN_PYTHON_EOF as an indicator to just close the method
+#
+# The two parts using it are tightly integrated anyway
+IN_PYTHON_EOF = -9999999999999
+
+
+
+def supports(fn, d):
+    """Return True if fn has a supported extension"""
+    return os.path.splitext(fn)[-1] in [".bb", ".bbclass", ".inc"]
+
+def inherit(files, fn, lineno, d):
+    __inherit_cache = d.getVar('__inherit_cache', False) or []
+    files = d.expand(files).split()
+    for file in files:
+        if not os.path.isabs(file) and not file.endswith(".bbclass"):
+            file = os.path.join('classes', '%s.bbclass' % file)
+
+        if not os.path.isabs(file):
+            bbpath = d.getVar("BBPATH", True)
+            abs_fn, attempts = bb.utils.which(bbpath, file, history=True)
+            for af in attempts:
+                if af != abs_fn:
+                    bb.parse.mark_dependency(d, af)
+            if abs_fn:
+                file = abs_fn
+
+        if not file in __inherit_cache:
+            logger.debug(1, "Inheriting %s (from %s:%d)" % (file, fn, lineno))
+            __inherit_cache.append( file )
+            d.setVar('__inherit_cache', __inherit_cache)
+            include(fn, file, lineno, d, "inherit")
+            __inherit_cache = d.getVar('__inherit_cache', False) or []
+
+def get_statements(filename, absolute_filename, base_name):
+    global cached_statements
+
+    try:
+        return cached_statements[absolute_filename]
+    except KeyError:
+        file = open(absolute_filename, 'r')
+        statements = ast.StatementGroup()
+
+        lineno = 0
+        while True:
+            lineno = lineno + 1
+            s = file.readline()
+            if not s: break
+            s = s.rstrip()
+            feeder(lineno, s, filename, base_name, statements)
+        file.close()
+        if __inpython__:
+            # add a blank line to close out any python definition
+            feeder(IN_PYTHON_EOF, "", filename, base_name, statements)
+
+        if filename.endswith(".bbclass") or filename.endswith(".inc"):
+            cached_statements[absolute_filename] = statements
+        return statements
+
+def handle(fn, d, include):
+    global __func_start_regexp__, __inherit_regexp__, __export_func_regexp__, __addtask_regexp__, __addhandler_regexp__, __infunc__, __body__, __residue__, __classname__
+    __body__ = []
+    __infunc__ = []
+    __classname__ = ""
+    __residue__ = []
+
+    base_name = os.path.basename(fn)
+    (root, ext) = os.path.splitext(base_name)
+    init(d)
+
+    if ext == ".bbclass":
+        __classname__ = root
+        __inherit_cache = d.getVar('__inherit_cache', False) or []
+        if not fn in __inherit_cache:
+            __inherit_cache.append(fn)
+            d.setVar('__inherit_cache', __inherit_cache)
+
+    if include != 0:
+        oldfile = d.getVar('FILE', False)
+    else:
+        oldfile = None
+
+    abs_fn = resolve_file(fn, d)
+
+    if include:
+        bb.parse.mark_dependency(d, abs_fn)
+
+    # actual loading
+    statements = get_statements(fn, abs_fn, base_name)
+
+    # DONE WITH PARSING... time to evaluate
+    if ext != ".bbclass" and abs_fn != oldfile:
+        d.setVar('FILE', abs_fn)
+
+    try:
+        statements.eval(d)
+    except bb.parse.SkipRecipe:
+        bb.data.setVar("__SKIPPED", True, d)
+        if include == 0:
+            return { "" : d }
+
+    if __infunc__:
+        raise ParseError("Shell function %s is never closed" % __infunc__[0], __infunc__[1], __infunc__[2])
+    if __residue__:
+        raise ParseError("Leftover unparsed (incomplete?) data %s from %s" % __residue__, fn)
+
+    if ext != ".bbclass" and include == 0:
+        return ast.multi_finalize(fn, d)
+
+    if ext != ".bbclass" and oldfile and abs_fn != oldfile:
+        d.setVar("FILE", oldfile)
+
+    return d
+
+def feeder(lineno, s, fn, root, statements):
+    global __func_start_regexp__, __inherit_regexp__, __export_func_regexp__, __addtask_regexp__, __addhandler_regexp__, __def_regexp__, __python_func_regexp__, __inpython__, __infunc__, __body__, bb, __residue__, __classname__
+    if __infunc__:
+        if s == '}':
+            __body__.append('')
+            ast.handleMethod(statements, fn, lineno, __infunc__[0], __body__)
+            __infunc__ = []
+            __body__ = []
+        else:
+            __body__.append(s)
+        return
+
+    if __inpython__:
+        m = __python_func_regexp__.match(s)
+        if m and lineno != IN_PYTHON_EOF:
+            __body__.append(s)
+            return
+        else:
+            ast.handlePythonMethod(statements, fn, lineno, __inpython__,
+                                   root, __body__)
+            __body__ = []
+            __inpython__ = False
+
+            if lineno == IN_PYTHON_EOF:
+                return
+
+    if s and s[0] == '#':
+        if len(__residue__) != 0 and __residue__[0][0] != "#":
+            bb.fatal("There is a comment on line %s of file %s (%s) which is in the middle of a multiline expression.\nBitbake used to ignore these but no longer does so, please fix your metadata as errors are likely as a result of this change." % (lineno, fn, s))
+
+    if len(__residue__) != 0 and __residue__[0][0] == "#" and (not s or s[0] != "#"):
+        bb.fatal("There is a confusing multiline, partially commented expression on line %s of file %s (%s).\nPlease clarify whether this is all a comment or should be parsed." % (lineno, fn, s))
+
+    if s and s[-1] == '\\':
+        __residue__.append(s[:-1])
+        return
+
+    s = "".join(__residue__) + s
+    __residue__ = []
+
+    # Skip empty lines
+    if s == '':
+        return   
+
+    # Skip comments
+    if s[0] == '#':
+        return
+
+    m = __func_start_regexp__.match(s)
+    if m:
+        __infunc__ = [m.group("func") or "__anonymous", fn, lineno]
+        ast.handleMethodFlags(statements, fn, lineno, __infunc__[0], m)
+        return
+
+    m = __def_regexp__.match(s)
+    if m:
+        __body__.append(s)
+        __inpython__ = m.group(1)
+
+        return
+
+    m = __export_func_regexp__.match(s)
+    if m:
+        ast.handleExportFuncs(statements, fn, lineno, m, __classname__)
+        return
+
+    m = __addtask_regexp__.match(s)
+    if m:
+        ast.handleAddTask(statements, fn, lineno, m)
+        return
+
+    m = __deltask_regexp__.match(s)
+    if m:
+        ast.handleDelTask(statements, fn, lineno, m)
+        return
+
+    m = __addhandler_regexp__.match(s)
+    if m:
+        ast.handleBBHandlers(statements, fn, lineno, m)
+        return
+
+    m = __inherit_regexp__.match(s)
+    if m:
+        ast.handleInherit(statements, fn, lineno, m)
+        return
+
+    return ConfHandler.feeder(lineno, s, fn, statements)
+
+# Add us to the handlers list
+from .. import handlers
+handlers.append({'supports': supports, 'handle': handle, 'init': init})
+del handlers
diff --git a/bitbake/lib/bb/parse/parse_py/ConfHandler.py b/bitbake/lib/bb/parse/parse_py/ConfHandler.py
new file mode 100644
index 0000000..fbd75b1
--- /dev/null
+++ b/bitbake/lib/bb/parse/parse_py/ConfHandler.py
@@ -0,0 +1,193 @@
+#!/usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+   class for handling configuration data files
+
+   Reads a .conf file and obtains its metadata
+
+"""
+
+# Copyright (C) 2003, 2004  Chris Larson
+# Copyright (C) 2003, 2004  Phil Blundell
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import errno
+import re
+import os
+import bb.utils
+from bb.parse import ParseError, resolve_file, ast, logger, handle
+
+__config_regexp__  = re.compile( r"""
+    ^
+    (?P<exp>export\s*)?
+    (?P<var>[a-zA-Z0-9\-~_+.${}/]+?)
+    (\[(?P<flag>[a-zA-Z0-9\-_+.]+)\])?
+
+    \s* (
+        (?P<colon>:=) |
+        (?P<lazyques>\?\?=) |
+        (?P<ques>\?=) |
+        (?P<append>\+=) |
+        (?P<prepend>=\+) |
+        (?P<predot>=\.) |
+        (?P<postdot>\.=) |
+        =
+    ) \s*
+
+    (?!'[^']*'[^']*'$)
+    (?!\"[^\"]*\"[^\"]*\"$)
+    (?P<apo>['\"])
+    (?P<value>.*)
+    (?P=apo)
+    $
+    """, re.X)
+__include_regexp__ = re.compile( r"include\s+(.+)" )
+__require_regexp__ = re.compile( r"require\s+(.+)" )
+__export_regexp__ = re.compile( r"export\s+([a-zA-Z0-9\-_+.${}/]+)$" )
+
+def init(data):
+    topdir = data.getVar('TOPDIR', False)
+    if not topdir:
+        data.setVar('TOPDIR', os.getcwd())
+
+
+def supports(fn, d):
+    return fn[-5:] == ".conf"
+
+def include(parentfn, fn, lineno, data, error_out):
+    """
+    error_out: A string indicating the verb (e.g. "include", "inherit") to be
+    used in a ParseError that will be raised if the file to be included could
+    not be included. Specify False to avoid raising an error in this case.
+    """
+    if parentfn == fn: # prevent infinite recursion
+        return None
+
+    fn = data.expand(fn)
+    parentfn = data.expand(parentfn)
+
+    if not os.path.isabs(fn):
+        dname = os.path.dirname(parentfn)
+        bbpath = "%s:%s" % (dname, data.getVar("BBPATH", True))
+        abs_fn, attempts = bb.utils.which(bbpath, fn, history=True)
+        if abs_fn and bb.parse.check_dependency(data, abs_fn):
+            logger.warn("Duplicate inclusion for %s in %s" % (abs_fn, data.getVar('FILE', True)))
+        for af in attempts:
+            bb.parse.mark_dependency(data, af)
+        if abs_fn:
+            fn = abs_fn
+    elif bb.parse.check_dependency(data, fn):
+        logger.warn("Duplicate inclusion for %s in %s" % (fn, data.getVar('FILE', True)))
+
+    try:
+        bb.parse.handle(fn, data, True)
+    except (IOError, OSError) as exc:
+        if exc.errno == errno.ENOENT:
+            if error_out:
+                raise ParseError("Could not %s file %s" % (error_out, fn), parentfn, lineno)
+            logger.debug(2, "CONF file '%s' not found", fn)
+        else:
+            if error_out:
+                raise ParseError("Could not %s file %s: %s" % (error_out, fn, exc.strerror), parentfn, lineno)
+            else:
+                raise ParseError("Error parsing %s: %s" % (fn, exc.strerror), parentfn, lineno)
+
+# We have an issue where a UI might want to enforce particular settings such as
+# an empty DISTRO variable. If configuration files do something like assigning
+# a weak default, it turns out to be very difficult to filter out these changes,
+# particularly when the weak default might appear half way though parsing a chain 
+# of configuration files. We therefore let the UIs hook into configuration file
+# parsing. This turns out to be a hard problem to solve any other way.
+confFilters = []
+
+def handle(fn, data, include):
+    init(data)
+
+    if include == 0:
+        oldfile = None
+    else:
+        oldfile = data.getVar('FILE', False)
+
+    abs_fn = resolve_file(fn, data)
+    f = open(abs_fn, 'r')
+
+    if include:
+        bb.parse.mark_dependency(data, abs_fn)
+
+    statements = ast.StatementGroup()
+    lineno = 0
+    while True:
+        lineno = lineno + 1
+        s = f.readline()
+        if not s:
+            break
+        w = s.strip()
+        # skip empty lines
+        if not w:
+            continue
+        s = s.rstrip()
+        while s[-1] == '\\':
+            s2 = f.readline().strip()
+            lineno = lineno + 1
+            if (not s2 or s2 and s2[0] != "#") and s[0] == "#" :
+                bb.fatal("There is a confusing multiline, partially commented expression on line %s of file %s (%s).\nPlease clarify whether this is all a comment or should be parsed." % (lineno, fn, s))
+            s = s[:-1] + s2
+        # skip comments
+        if s[0] == '#':
+            continue
+        feeder(lineno, s, abs_fn, statements)
+
+    # DONE WITH PARSING... time to evaluate
+    data.setVar('FILE', abs_fn)
+    statements.eval(data)
+    if oldfile:
+        data.setVar('FILE', oldfile)
+
+    f.close()
+
+    for f in confFilters:
+        f(fn, data)
+
+    return data
+
+def feeder(lineno, s, fn, statements):
+    m = __config_regexp__.match(s)
+    if m:
+        groupd = m.groupdict()
+        ast.handleData(statements, fn, lineno, groupd)
+        return
+
+    m = __include_regexp__.match(s)
+    if m:
+        ast.handleInclude(statements, fn, lineno, m, False)
+        return
+
+    m = __require_regexp__.match(s)
+    if m:
+        ast.handleInclude(statements, fn, lineno, m, True)
+        return
+
+    m = __export_regexp__.match(s)
+    if m:
+        ast.handleExport(statements, fn, lineno, m)
+        return
+
+    raise ParseError("unparsed line: '%s'" % s, fn, lineno);
+
+# Add us to the handlers list
+from bb.parse import handlers
+handlers.append({'supports': supports, 'handle': handle, 'init': init})
+del handlers
diff --git a/bitbake/lib/bb/parse/parse_py/__init__.py b/bitbake/lib/bb/parse/parse_py/__init__.py
new file mode 100644
index 0000000..3e658d0
--- /dev/null
+++ b/bitbake/lib/bb/parse/parse_py/__init__.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake Parsers
+
+File parsers for the BitBake build tools.
+
+"""
+
+# Copyright (C) 2003, 2004  Chris Larson
+# Copyright (C) 2003, 2004  Phil Blundell
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Based on functions from the base bb module, Copyright 2003 Holger Schurig
+
+from __future__ import absolute_import
+from . import ConfHandler
+from . import BBHandler
+
+__version__ = '1.0'
diff --git a/bitbake/lib/bb/persist_data.py b/bitbake/lib/bb/persist_data.py
new file mode 100644
index 0000000..5795bc8
--- /dev/null
+++ b/bitbake/lib/bb/persist_data.py
@@ -0,0 +1,217 @@
+"""BitBake Persistent Data Store
+
+Used to store data in a central location such that other threads/tasks can
+access them at some future date.  Acts as a convenience wrapper around sqlite,
+currently, providing a key/value store accessed by 'domain'.
+"""
+
+# Copyright (C) 2007        Richard Purdie
+# Copyright (C) 2010        Chris Larson <chris_larson@mentor.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import collections
+import logging
+import os.path
+import sys
+import warnings
+from bb.compat import total_ordering
+from collections import Mapping
+
+try:
+    import sqlite3
+except ImportError:
+    from pysqlite2 import dbapi2 as sqlite3
+
+sqlversion = sqlite3.sqlite_version_info
+if sqlversion[0] < 3 or (sqlversion[0] == 3 and sqlversion[1] < 3):
+    raise Exception("sqlite3 version 3.3.0 or later is required.")
+
+
+logger = logging.getLogger("BitBake.PersistData")
+if hasattr(sqlite3, 'enable_shared_cache'):
+    try:
+        sqlite3.enable_shared_cache(True)
+    except sqlite3.OperationalError:
+        pass
+
+
+@total_ordering
+class SQLTable(collections.MutableMapping):
+    """Object representing a table/domain in the database"""
+    def __init__(self, cachefile, table):
+        self.cachefile = cachefile
+        self.table = table
+        self.cursor = connect(self.cachefile)
+
+        self._execute("CREATE TABLE IF NOT EXISTS %s(key TEXT, value TEXT);"
+                      % table)
+
+    def _execute(self, *query):
+        """Execute a query, waiting to acquire a lock if necessary"""
+        count = 0
+        while True:
+            try:
+                return self.cursor.execute(*query)
+            except sqlite3.OperationalError as exc:
+                if 'database is locked' in str(exc) and count < 500:
+                    count = count + 1
+                    self.cursor.close()
+                    self.cursor = connect(self.cachefile)
+                    continue
+                raise
+
+    def __enter__(self):
+        self.cursor.__enter__()
+        return self
+
+    def __exit__(self, *excinfo):
+        self.cursor.__exit__(*excinfo)
+
+    def __getitem__(self, key):
+        data = self._execute("SELECT * from %s where key=?;" %
+                             self.table, [key])
+        for row in data:
+            return row[1]
+        raise KeyError(key)
+
+    def __delitem__(self, key):
+        if key not in self:
+            raise KeyError(key)
+        self._execute("DELETE from %s where key=?;" % self.table, [key])
+
+    def __setitem__(self, key, value):
+        if not isinstance(key, basestring):
+            raise TypeError('Only string keys are supported')
+        elif not isinstance(value, basestring):
+            raise TypeError('Only string values are supported')
+
+        data = self._execute("SELECT * from %s where key=?;" %
+                                   self.table, [key])
+        exists = len(list(data))
+        if exists:
+            self._execute("UPDATE %s SET value=? WHERE key=?;" % self.table,
+                          [value, key])
+        else:
+            self._execute("INSERT into %s(key, value) values (?, ?);" %
+                          self.table, [key, value])
+
+    def __contains__(self, key):
+        return key in set(self)
+
+    def __len__(self):
+        data = self._execute("SELECT COUNT(key) FROM %s;" % self.table)
+        for row in data:
+            return row[0]
+
+    def __iter__(self):
+        data = self._execute("SELECT key FROM %s;" % self.table)
+        return (row[0] for row in data)
+
+    def __lt__(self, other):
+        if not isinstance(other, Mapping):
+            raise NotImplemented
+
+        return len(self) < len(other)
+
+    def get_by_pattern(self, pattern):
+        data = self._execute("SELECT * FROM %s WHERE key LIKE ?;" %
+                             self.table, [pattern])
+        return [row[1] for row in data]
+
+    def values(self):
+        return list(self.itervalues())
+
+    def itervalues(self):
+        data = self._execute("SELECT value FROM %s;" % self.table)
+        return (row[0] for row in data)
+
+    def items(self):
+        return list(self.iteritems())
+
+    def iteritems(self):
+        return self._execute("SELECT * FROM %s;" % self.table)
+
+    def clear(self):
+        self._execute("DELETE FROM %s;" % self.table)
+
+    def has_key(self, key):
+        return key in self
+
+
+class PersistData(object):
+    """Deprecated representation of the bitbake persistent data store"""
+    def __init__(self, d):
+        warnings.warn("Use of PersistData is deprecated.  Please use "
+                      "persist(domain, d) instead.",
+                      category=DeprecationWarning,
+                      stacklevel=2)
+
+        self.data = persist(d)
+        logger.debug(1, "Using '%s' as the persistent data cache",
+                     self.data.filename)
+
+    def addDomain(self, domain):
+        """
+        Add a domain (pending deprecation)
+        """
+        return self.data[domain]
+
+    def delDomain(self, domain):
+        """
+        Removes a domain and all the data it contains
+        """
+        del self.data[domain]
+
+    def getKeyValues(self, domain):
+        """
+        Return a list of key + value pairs for a domain
+        """
+        return self.data[domain].items()
+
+    def getValue(self, domain, key):
+        """
+        Return the value of a key for a domain
+        """
+        return self.data[domain][key]
+
+    def setValue(self, domain, key, value):
+        """
+        Sets the value of a key for a domain
+        """
+        self.data[domain][key] = value
+
+    def delValue(self, domain, key):
+        """
+        Deletes a key/value pair
+        """
+        del self.data[domain][key]
+
+def connect(database):
+    connection = sqlite3.connect(database, timeout=5, isolation_level=None)
+    connection.execute("pragma synchronous = off;")
+    return connection
+
+def persist(domain, d):
+    """Convenience factory for SQLTable objects based upon metadata"""
+    import bb.utils
+    cachedir = (d.getVar("PERSISTENT_DIR", True) or
+                d.getVar("CACHE", True))
+    if not cachedir:
+        logger.critical("Please set the 'PERSISTENT_DIR' or 'CACHE' variable")
+        sys.exit(1)
+
+    bb.utils.mkdirhier(cachedir)
+    cachefile = os.path.join(cachedir, "bb_persist_data.sqlite3")
+    return SQLTable(cachefile, domain)
diff --git a/bitbake/lib/bb/process.py b/bitbake/lib/bb/process.py
new file mode 100644
index 0000000..1c07f2d
--- /dev/null
+++ b/bitbake/lib/bb/process.py
@@ -0,0 +1,156 @@
+import logging
+import signal
+import subprocess
+import errno
+import select
+
+logger = logging.getLogger('BitBake.Process')
+
+def subprocess_setup():
+    # Python installs a SIGPIPE handler by default. This is usually not what
+    # non-Python subprocesses expect.
+    signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+
+class CmdError(RuntimeError):
+    def __init__(self, command, msg=None):
+        self.command = command
+        self.msg = msg
+
+    def __str__(self):
+        if not isinstance(self.command, basestring):
+            cmd = subprocess.list2cmdline(self.command)
+        else:
+            cmd = self.command
+
+        msg = "Execution of '%s' failed" % cmd
+        if self.msg:
+            msg += ': %s' % self.msg
+        return msg
+
+class NotFoundError(CmdError):
+    def __str__(self):
+        return CmdError.__str__(self) + ": command not found"
+
+class ExecutionError(CmdError):
+    def __init__(self, command, exitcode, stdout = None, stderr = None):
+        CmdError.__init__(self, command)
+        self.exitcode = exitcode
+        self.stdout = stdout
+        self.stderr = stderr
+
+    def __str__(self):
+        message = ""
+        if self.stderr:
+            message += self.stderr
+        if self.stdout:
+            message += self.stdout
+        if message:
+            message = ":\n" + message
+        return (CmdError.__str__(self) +
+                " with exit code %s" % self.exitcode + message)
+
+class Popen(subprocess.Popen):
+    defaults = {
+        "close_fds": True,
+        "preexec_fn": subprocess_setup,
+        "stdout": subprocess.PIPE,
+        "stderr": subprocess.STDOUT,
+        "stdin": subprocess.PIPE,
+        "shell": False,
+    }
+
+    def __init__(self, *args, **kwargs):
+        options = dict(self.defaults)
+        options.update(kwargs)
+        subprocess.Popen.__init__(self, *args, **options)
+
+def _logged_communicate(pipe, log, input, extrafiles):
+    if pipe.stdin:
+        if input is not None:
+            pipe.stdin.write(input)
+        pipe.stdin.close()
+
+    outdata, errdata = [], []
+    rin = []
+
+    if pipe.stdout is not None:
+        bb.utils.nonblockingfd(pipe.stdout.fileno())
+        rin.append(pipe.stdout)
+    if pipe.stderr is not None:
+        bb.utils.nonblockingfd(pipe.stderr.fileno())
+        rin.append(pipe.stderr)
+    for fobj, _ in extrafiles:
+        bb.utils.nonblockingfd(fobj.fileno())
+        rin.append(fobj)
+
+    def readextras(selected):
+        for fobj, func in extrafiles:
+            if fobj in selected:
+                try:
+                    data = fobj.read()
+                except IOError as err:
+                    if err.errno == errno.EAGAIN or err.errno == errno.EWOULDBLOCK:
+                        data = None
+                if data is not None:
+                    func(data)
+
+    try:
+        while pipe.poll() is None:
+            rlist = rin
+            try:
+                r,w,e = select.select (rlist, [], [], 1)
+            except OSError as e:
+                if e.errno != errno.EINTR:
+                    raise
+
+            if pipe.stdout in r:
+                data = pipe.stdout.read()
+                if data is not None:
+                    outdata.append(data)
+                    log.write(data)
+
+            if pipe.stderr in r:
+                data = pipe.stderr.read()
+                if data is not None:
+                    errdata.append(data)
+                    log.write(data)
+
+            readextras(r)
+
+    finally:    
+        log.flush()
+
+    readextras([fobj for fobj, _ in extrafiles])
+
+    if pipe.stdout is not None:
+        pipe.stdout.close()
+    if pipe.stderr is not None:
+        pipe.stderr.close()
+    return ''.join(outdata), ''.join(errdata)
+
+def run(cmd, input=None, log=None, extrafiles=None, **options):
+    """Convenience function to run a command and return its output, raising an
+    exception when the command fails"""
+
+    if not extrafiles:
+        extrafiles = []
+
+    if isinstance(cmd, basestring) and not "shell" in options:
+        options["shell"] = True
+
+    try:
+        pipe = Popen(cmd, **options)
+    except OSError as exc:
+        if exc.errno == 2:
+            raise NotFoundError(cmd)
+        else:
+            raise CmdError(cmd, exc)
+
+    if log:
+        stdout, stderr = _logged_communicate(pipe, log, input, extrafiles)
+    else:
+        stdout, stderr = pipe.communicate(input)
+
+    if pipe.returncode != 0:
+        raise ExecutionError(cmd, pipe.returncode, stdout, stderr)
+    return stdout, stderr
diff --git a/bitbake/lib/bb/providers.py b/bitbake/lib/bb/providers.py
new file mode 100644
index 0000000..637e1fa
--- /dev/null
+++ b/bitbake/lib/bb/providers.py
@@ -0,0 +1,381 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Copyright (C) 2003, 2004  Chris Larson
+# Copyright (C) 2003, 2004  Phil Blundell
+# Copyright (C) 2003 - 2005 Michael 'Mickey' Lauer
+# Copyright (C) 2005        Holger Hans Peter Freyther
+# Copyright (C) 2005        ROAD GmbH
+# Copyright (C) 2006        Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import re
+import logging
+from bb import data, utils
+from collections import defaultdict
+import bb
+
+logger = logging.getLogger("BitBake.Provider")
+
+class NoProvider(bb.BBHandledException):
+    """Exception raised when no provider of a build dependency can be found"""
+
+class NoRProvider(bb.BBHandledException):
+    """Exception raised when no provider of a runtime dependency can be found"""
+
+class MultipleRProvider(bb.BBHandledException):
+    """Exception raised when multiple providers of a runtime dependency can be found"""
+
+def findProviders(cfgData, dataCache, pkg_pn = None):
+    """
+    Convenience function to get latest and preferred providers in pkg_pn
+    """
+
+    if not pkg_pn:
+        pkg_pn = dataCache.pkg_pn
+
+    # Need to ensure data store is expanded
+    localdata = data.createCopy(cfgData)
+    bb.data.update_data(localdata)
+    bb.data.expandKeys(localdata)
+
+    preferred_versions = {}
+    latest_versions = {}
+
+    for pn in pkg_pn:
+        (last_ver, last_file, pref_ver, pref_file) = findBestProvider(pn, localdata, dataCache, pkg_pn)
+        preferred_versions[pn] = (pref_ver, pref_file)
+        latest_versions[pn] = (last_ver, last_file)
+
+    return (latest_versions, preferred_versions)
+
+
+def allProviders(dataCache):
+    """
+    Find all providers for each pn
+    """
+    all_providers = defaultdict(list)
+    for (fn, pn) in dataCache.pkg_fn.items():
+        ver = dataCache.pkg_pepvpr[fn]
+        all_providers[pn].append((ver, fn))
+    return all_providers
+
+
+def sortPriorities(pn, dataCache, pkg_pn = None):
+    """
+    Reorder pkg_pn by file priority and default preference
+    """
+
+    if not pkg_pn:
+        pkg_pn = dataCache.pkg_pn
+
+    files = pkg_pn[pn]
+    priorities = {}
+    for f in files:
+        priority = dataCache.bbfile_priority[f]
+        preference = dataCache.pkg_dp[f]
+        if priority not in priorities:
+            priorities[priority] = {}
+        if preference not in priorities[priority]:
+            priorities[priority][preference] = []
+        priorities[priority][preference].append(f)
+    tmp_pn = []
+    for pri in sorted(priorities):
+        tmp_pref = []
+        for pref in sorted(priorities[pri]):
+            tmp_pref.extend(priorities[pri][pref])
+        tmp_pn = [tmp_pref] + tmp_pn
+
+    return tmp_pn
+
+def preferredVersionMatch(pe, pv, pr, preferred_e, preferred_v, preferred_r):
+    """
+    Check if the version pe,pv,pr is the preferred one.
+    If there is preferred version defined and ends with '%', then pv has to start with that version after removing the '%'
+    """
+    if (pr == preferred_r or preferred_r == None):
+        if (pe == preferred_e or preferred_e == None):
+            if preferred_v == pv:
+                return True
+            if preferred_v != None and preferred_v.endswith('%') and pv.startswith(preferred_v[:len(preferred_v)-1]):
+                return True
+    return False
+
+def findPreferredProvider(pn, cfgData, dataCache, pkg_pn = None, item = None):
+    """
+    Find the first provider in pkg_pn with a PREFERRED_VERSION set.
+    """
+
+    preferred_file = None
+    preferred_ver = None
+
+    localdata = data.createCopy(cfgData)
+    localdata.setVar('OVERRIDES', "%s:pn-%s:%s" % (data.getVar('OVERRIDES', localdata), pn, pn))
+    bb.data.update_data(localdata)
+
+    preferred_v = localdata.getVar('PREFERRED_VERSION', True)
+    if preferred_v:
+        m = re.match('(\d+:)*(.*)(_.*)*', preferred_v)
+        if m:
+            if m.group(1):
+                preferred_e = m.group(1)[:-1]
+            else:
+                preferred_e = None
+            preferred_v = m.group(2)
+            if m.group(3):
+                preferred_r = m.group(3)[1:]
+            else:
+                preferred_r = None
+        else:
+            preferred_e = None
+            preferred_r = None
+
+        for file_set in pkg_pn:
+            for f in file_set:
+                pe, pv, pr = dataCache.pkg_pepvpr[f]
+                if preferredVersionMatch(pe, pv, pr, preferred_e, preferred_v, preferred_r):
+                    preferred_file = f
+                    preferred_ver = (pe, pv, pr)
+                    break
+            if preferred_file:
+                break;
+        if preferred_r:
+            pv_str = '%s-%s' % (preferred_v, preferred_r)
+        else:
+            pv_str = preferred_v
+        if not (preferred_e is None):
+            pv_str = '%s:%s' % (preferred_e, pv_str)
+        itemstr = ""
+        if item:
+            itemstr = " (for item %s)" % item
+        if preferred_file is None:
+            logger.info("preferred version %s of %s not available%s", pv_str, pn, itemstr)
+            available_vers = []
+            for file_set in pkg_pn:
+                for f in file_set:
+                    pe, pv, pr = dataCache.pkg_pepvpr[f]
+                    ver_str = pv
+                    if pe:
+                        ver_str = "%s:%s" % (pe, ver_str)
+                    if not ver_str in available_vers:
+                        available_vers.append(ver_str)
+            if available_vers:
+                available_vers.sort()
+                logger.info("versions of %s available: %s", pn, ' '.join(available_vers))
+        else:
+            logger.debug(1, "selecting %s as PREFERRED_VERSION %s of package %s%s", preferred_file, pv_str, pn, itemstr)
+
+    return (preferred_ver, preferred_file)
+
+
+def findLatestProvider(pn, cfgData, dataCache, file_set):
+    """
+    Return the highest version of the providers in file_set.
+    Take default preferences into account.
+    """
+    latest = None
+    latest_p = 0
+    latest_f = None
+    for file_name in file_set:
+        pe, pv, pr = dataCache.pkg_pepvpr[file_name]
+        dp = dataCache.pkg_dp[file_name]
+
+        if (latest is None) or ((latest_p == dp) and (utils.vercmp(latest, (pe, pv, pr)) < 0)) or (dp > latest_p):
+            latest = (pe, pv, pr)
+            latest_f = file_name
+            latest_p = dp
+
+    return (latest, latest_f)
+
+
+def findBestProvider(pn, cfgData, dataCache, pkg_pn = None, item = None):
+    """
+    If there is a PREFERRED_VERSION, find the highest-priority bbfile
+    providing that version.  If not, find the latest version provided by
+    an bbfile in the highest-priority set.
+    """
+
+    sortpkg_pn = sortPriorities(pn, dataCache, pkg_pn)
+    # Find the highest priority provider with a PREFERRED_VERSION set
+    (preferred_ver, preferred_file) = findPreferredProvider(pn, cfgData, dataCache, sortpkg_pn, item)
+    # Find the latest version of the highest priority provider
+    (latest, latest_f) = findLatestProvider(pn, cfgData, dataCache, sortpkg_pn[0])
+
+    if preferred_file is None:
+        preferred_file = latest_f
+        preferred_ver = latest
+
+    return (latest, latest_f, preferred_ver, preferred_file)
+
+
+def _filterProviders(providers, item, cfgData, dataCache):
+    """
+    Take a list of providers and filter/reorder according to the
+    environment variables and previous build results
+    """
+    eligible = []
+    preferred_versions = {}
+    sortpkg_pn = {}
+
+    # The order of providers depends on the order of the files on the disk
+    # up to here. Sort pkg_pn to make dependency issues reproducible rather
+    # than effectively random.
+    providers.sort()
+
+    # Collate providers by PN
+    pkg_pn = {}
+    for p in providers:
+        pn = dataCache.pkg_fn[p]
+        if pn not in pkg_pn:
+            pkg_pn[pn] = []
+        pkg_pn[pn].append(p)
+
+    logger.debug(1, "providers for %s are: %s", item, pkg_pn.keys())
+
+    # First add PREFERRED_VERSIONS
+    for pn in pkg_pn:
+        sortpkg_pn[pn] = sortPriorities(pn, dataCache, pkg_pn)
+        preferred_versions[pn] = findPreferredProvider(pn, cfgData, dataCache, sortpkg_pn[pn], item)
+        if preferred_versions[pn][1]:
+            eligible.append(preferred_versions[pn][1])
+
+    # Now add latest versions
+    for pn in sortpkg_pn:
+        if pn in preferred_versions and preferred_versions[pn][1]:
+            continue
+        preferred_versions[pn] = findLatestProvider(pn, cfgData, dataCache, sortpkg_pn[pn][0])
+        eligible.append(preferred_versions[pn][1])
+
+    if len(eligible) == 0:
+        logger.error("no eligible providers for %s", item)
+        return 0
+
+    # If pn == item, give it a slight default preference
+    # This means PREFERRED_PROVIDER_foobar defaults to foobar if available
+    for p in providers:
+        pn = dataCache.pkg_fn[p]
+        if pn != item:
+            continue
+        (newvers, fn) = preferred_versions[pn]
+        if not fn in eligible:
+            continue
+        eligible.remove(fn)
+        eligible = [fn] + eligible
+
+    return eligible
+
+
+def filterProviders(providers, item, cfgData, dataCache):
+    """
+    Take a list of providers and filter/reorder according to the
+    environment variables and previous build results
+    Takes a "normal" target item
+    """
+
+    eligible = _filterProviders(providers, item, cfgData, dataCache)
+
+    prefervar = cfgData.getVar('PREFERRED_PROVIDER_%s' % item, True)
+    if prefervar:
+        dataCache.preferred[item] = prefervar
+
+    foundUnique = False
+    if item in dataCache.preferred:
+        for p in eligible:
+            pn = dataCache.pkg_fn[p]
+            if dataCache.preferred[item] == pn:
+                logger.verbose("selecting %s to satisfy %s due to PREFERRED_PROVIDERS", pn, item)
+                eligible.remove(p)
+                eligible = [p] + eligible
+                foundUnique = True
+                break
+
+    logger.debug(1, "sorted providers for %s are: %s", item, eligible)
+
+    return eligible, foundUnique
+
+def filterProvidersRunTime(providers, item, cfgData, dataCache):
+    """
+    Take a list of providers and filter/reorder according to the
+    environment variables and previous build results
+    Takes a "runtime" target item
+    """
+
+    eligible = _filterProviders(providers, item, cfgData, dataCache)
+
+    # Should use dataCache.preferred here?
+    preferred = []
+    preferred_vars = []
+    pns = {}
+    for p in eligible:
+        pns[dataCache.pkg_fn[p]] = p
+    for p in eligible:
+        pn = dataCache.pkg_fn[p]
+        provides = dataCache.pn_provides[pn]
+        for provide in provides:
+            prefervar = cfgData.getVar('PREFERRED_PROVIDER_%s' % provide, True)
+            #logger.debug(1, "checking PREFERRED_PROVIDER_%s (value %s) against %s", provide, prefervar, pns.keys())
+            if prefervar in pns and pns[prefervar] not in preferred:
+                var = "PREFERRED_PROVIDER_%s = %s" % (provide, prefervar)
+                logger.verbose("selecting %s to satisfy runtime %s due to %s", prefervar, item, var)
+                preferred_vars.append(var)
+                pref = pns[prefervar]
+                eligible.remove(pref)
+                eligible = [pref] + eligible
+                preferred.append(pref)
+                break
+
+    numberPreferred = len(preferred)
+
+    if numberPreferred > 1:
+        logger.error("Trying to resolve runtime dependency %s resulted in conflicting PREFERRED_PROVIDER entries being found.\nThe providers found were: %s\nThe PREFERRED_PROVIDER entries resulting in this conflict were: %s", item, preferred, preferred_vars)
+
+    logger.debug(1, "sorted runtime providers for %s are: %s", item, eligible)
+
+    return eligible, numberPreferred
+
+regexp_cache = {}
+
+def getRuntimeProviders(dataCache, rdepend):
+    """
+    Return any providers of runtime dependency
+    """
+    rproviders = []
+
+    if rdepend in dataCache.rproviders:
+        rproviders += dataCache.rproviders[rdepend]
+
+    if rdepend in dataCache.packages:
+        rproviders += dataCache.packages[rdepend]
+
+    if rproviders:
+        return rproviders
+
+    # Only search dynamic packages if we can't find anything in other variables
+    for pattern in dataCache.packages_dynamic:
+        pattern = pattern.replace('+', "\+")
+        if pattern in regexp_cache:
+            regexp = regexp_cache[pattern]
+        else:
+            try:
+                regexp = re.compile(pattern)
+            except:
+                logger.error("Error parsing regular expression '%s'", pattern)
+                raise
+            regexp_cache[pattern] = regexp
+        if regexp.match(rdepend):
+            rproviders += dataCache.packages_dynamic[pattern]
+            logger.debug(1, "Assuming %s is a dynamic package, but it may not exist" % rdepend)
+
+    return rproviders
diff --git a/bitbake/lib/bb/pysh/__init__.py b/bitbake/lib/bb/pysh/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bitbake/lib/bb/pysh/__init__.py
diff --git a/bitbake/lib/bb/pysh/builtin.py b/bitbake/lib/bb/pysh/builtin.py
new file mode 100644
index 0000000..b748e4a
--- /dev/null
+++ b/bitbake/lib/bb/pysh/builtin.py
@@ -0,0 +1,710 @@
+# builtin.py - builtins and utilities definitions for pysh.
+#
+# Copyright 2007 Patrick Mezard
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+"""Builtin and internal utilities implementations.
+
+- Beware not to use python interpreter environment as if it were the shell
+environment. For instance, commands working directory must be explicitely handled
+through env['PWD'] instead of relying on python working directory.
+"""
+import errno
+import optparse
+import os
+import re
+import subprocess
+import sys
+import time
+
+def has_subprocess_bug():
+    return getattr(subprocess, 'list2cmdline') and \
+       (    subprocess.list2cmdline(['']) == '' or \
+            subprocess.list2cmdline(['foo|bar']) == 'foo|bar')
+            
+# Detect python bug 1634343: "subprocess swallows empty arguments under win32"
+# <http://sourceforge.net/tracker/index.php?func=detail&aid=1634343&group_id=5470&atid=105470>
+# Also detect: "[ 1710802 ] subprocess must escape redirection characters under win32"
+# <http://sourceforge.net/tracker/index.php?func=detail&aid=1710802&group_id=5470&atid=105470>
+if has_subprocess_bug():
+    import subprocess_fix
+    subprocess.list2cmdline = subprocess_fix.list2cmdline
+
+from sherrors import *
+
+class NonExitingParser(optparse.OptionParser):
+    """OptionParser default behaviour upon error is to print the error message and
+    exit. Raise a utility error instead.
+    """
+    def error(self, msg):
+        raise UtilityError(msg)
+
+#-------------------------------------------------------------------------------  
+# set special builtin
+#-------------------------------------------------------------------------------  
+OPT_SET = NonExitingParser(usage="set - set or unset options and positional parameters")
+OPT_SET.add_option( '-f', action='store_true', dest='has_f', default=False,
+    help='The shell shall disable pathname expansion.')
+OPT_SET.add_option('-e', action='store_true', dest='has_e', default=False,
+    help="""When this option is on, if a simple command fails for any of the \
+    reasons listed in Consequences of Shell Errors or returns an exit status \
+    value >0, and is not part of the compound list following a while, until, \
+    or if keyword, and is not a part of an AND or OR list, and is not a \
+    pipeline preceded by the ! reserved word, then the shell shall immediately \
+    exit.""")
+OPT_SET.add_option('-x', action='store_true', dest='has_x', default=False,
+    help="""The shell shall write to standard error a trace for each command \
+    after it expands the command and before it executes it. It is unspecified \
+    whether the command that turns tracing off is traced.""")
+
+def builtin_set(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+
+    option, args = OPT_SET.parse_args(args)
+    env = interp.get_env()
+    
+    if option.has_f:
+        env.set_opt('-f')
+    if option.has_e:
+        env.set_opt('-e')
+    if option.has_x:
+        env.set_opt('-x')
+    return 0
+    
+#-------------------------------------------------------------------------------  
+# shift special builtin
+#-------------------------------------------------------------------------------  
+def builtin_shift(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+        
+    params = interp.get_env().get_positional_args()
+    if args:
+        try:
+            n = int(args[0])
+            if n > len(params):
+                raise ValueError()
+        except ValueError:
+            return 1
+    else:
+        n = 1
+        
+    params[:n] = []
+    interp.get_env().set_positional_args(params)
+    return 0
+    
+#-------------------------------------------------------------------------------  
+# export special builtin
+#-------------------------------------------------------------------------------  
+OPT_EXPORT = NonExitingParser(usage="set - set or unset options and positional parameters")
+OPT_EXPORT.add_option('-p', action='store_true', dest='has_p', default=False)
+
+def builtin_export(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+        
+    option, args = OPT_EXPORT.parse_args(args)
+    if option.has_p:
+        raise NotImplementedError()
+    
+    for arg in args:
+        try:
+            name, value = arg.split('=', 1)
+        except ValueError:
+            name, value = arg, None
+        env = interp.get_env().export(name, value)
+    
+    return 0
+    
+#-------------------------------------------------------------------------------  
+# return special builtin
+#-------------------------------------------------------------------------------  
+def builtin_return(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+    res = 0
+    if args:
+        try:
+            res = int(args[0])
+        except ValueError:
+            res = 0
+        if not 0<=res<=255:
+            res = 0
+            
+    # BUG: should be last executed command exit code        
+    raise ReturnSignal(res)
+
+#-------------------------------------------------------------------------------  
+# trap special builtin
+#-------------------------------------------------------------------------------  
+def builtin_trap(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+    if len(args) < 2:
+        stderr.write('trap: usage: trap [[arg] signal_spec ...]\n')
+        return 2
+
+    action = args[0]
+    for sig in args[1:]:
+        try:
+            env.traps[sig] = action
+        except Exception as e:
+            stderr.write('trap: %s\n' % str(e))
+    return 0
+
+#-------------------------------------------------------------------------------  
+# unset special builtin
+#-------------------------------------------------------------------------------
+OPT_UNSET = NonExitingParser("unset - unset values and attributes of variables and functions")
+OPT_UNSET.add_option( '-f', action='store_true', dest='has_f', default=False)
+OPT_UNSET.add_option( '-v', action='store_true', dest='has_v', default=False)
+
+def builtin_unset(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+        
+    option, args = OPT_UNSET.parse_args(args)
+    
+    status = 0
+    env = interp.get_env()
+    for arg in args:    
+        try:
+            if option.has_f:
+                env.remove_function(arg)
+            else:
+                del env[arg]
+        except KeyError:
+            pass
+        except VarAssignmentError:
+            status = 1
+            
+    return status
+
+#-------------------------------------------------------------------------------  
+# wait special builtin
+#-------------------------------------------------------------------------------  
+def builtin_wait(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+
+    return interp.wait([int(arg) for arg in args])
+
+#-------------------------------------------------------------------------------  
+# cat utility
+#-------------------------------------------------------------------------------
+def utility_cat(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+
+    if not args:
+        args = ['-']
+
+    status = 0
+    for arg in args:
+        if arg == '-':
+            data = stdin.read()
+        else:
+            path = os.path.join(env['PWD'], arg)
+            try:
+                f = file(path, 'rb')
+                try:
+                    data = f.read()
+                finally:
+                    f.close()
+            except IOError as e:
+                if e.errno != errno.ENOENT:
+                    raise
+                status = 1
+                continue
+        stdout.write(data)
+        stdout.flush()
+    return status
+    
+#-------------------------------------------------------------------------------  
+# cd utility
+#-------------------------------------------------------------------------------  
+OPT_CD = NonExitingParser("cd - change the working directory")
+
+def utility_cd(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+
+    option, args = OPT_CD.parse_args(args)
+    env = interp.get_env()
+    
+    directory = None
+    printdir = False
+    if not args:
+        home = env.get('HOME')
+        if home:
+            # Unspecified, do nothing
+            return 0
+        else:
+            directory = home
+    elif len(args)==1:
+        directory = args[0]
+        if directory=='-':
+            if 'OLDPWD' not in env:
+                raise UtilityError("OLDPWD not set")
+            printdir = True
+            directory = env['OLDPWD']
+    else:
+        raise UtilityError("too many arguments")
+            
+    curpath = None
+    # Absolute directories will be handled correctly by the os.path.join call.
+    if not directory.startswith('.') and not directory.startswith('..'):
+        cdpaths = env.get('CDPATH', '.').split(';')
+        for cdpath in cdpaths:
+            p = os.path.join(cdpath, directory)
+            if os.path.isdir(p):
+                curpath = p
+                break
+    
+    if curpath is None:
+        curpath = directory
+    curpath = os.path.join(env['PWD'], directory)
+
+    env['OLDPWD'] = env['PWD']
+    env['PWD'] = curpath
+    if printdir:
+        stdout.write('%s\n' % curpath)
+    return 0
+
+#-------------------------------------------------------------------------------  
+# colon utility
+#-------------------------------------------------------------------------------  
+def utility_colon(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+    return 0
+    
+#-------------------------------------------------------------------------------  
+# echo utility
+#-------------------------------------------------------------------------------  
+def utility_echo(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+        
+    # Echo only takes arguments, no options. Use printf if you need fancy stuff.
+    output = ' '.join(args) + '\n'
+    stdout.write(output)
+    stdout.flush()
+    return 0
+    
+#-------------------------------------------------------------------------------  
+# egrep utility
+#-------------------------------------------------------------------------------
+# egrep is usually a shell script.
+# Unfortunately, pysh does not support shell scripts *with arguments* right now,
+# so the redirection is implemented here, assuming grep is available.
+def utility_egrep(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+        
+    return run_command('grep', ['-E'] + args, interp, env, stdin, stdout, 
+        stderr, debugflags)
+    
+#-------------------------------------------------------------------------------  
+# env utility
+#-------------------------------------------------------------------------------  
+def utility_env(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+    
+    if args and args[0]=='-i':
+        raise NotImplementedError('env: -i option is not implemented')
+    
+    i = 0
+    for arg in args:
+        if '=' not in arg:
+            break
+        # Update the current environment
+        name, value = arg.split('=', 1)
+        env[name] = value
+        i += 1
+        
+    if args[i:]:
+        # Find then execute the specified interpreter
+        utility = env.find_in_path(args[i])
+        if not utility:
+            return 127
+        args[i:i+1] = utility
+        name = args[i]
+        args = args[i+1:]
+        try:
+            return run_command(name, args, interp, env, stdin, stdout, stderr, 
+                debugflags)
+        except UtilityError:
+            stderr.write('env: failed to execute %s' % ' '.join([name]+args))
+            return 126            
+    else:
+        for pair in env.get_variables().iteritems():
+            stdout.write('%s=%s\n' % pair)
+    return 0
+    
+#-------------------------------------------------------------------------------  
+# exit utility
+#-------------------------------------------------------------------------------
+def utility_exit(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+        
+    res = None
+    if args:
+        try:
+            res = int(args[0])
+        except ValueError:
+            res = None
+        if not 0<=res<=255:
+            res = None
+            
+    if res is None:
+        # BUG: should be last executed command exit code
+        res = 0
+        
+    raise ExitSignal(res)
+
+#-------------------------------------------------------------------------------  
+# fgrep utility
+#-------------------------------------------------------------------------------
+# see egrep
+def utility_fgrep(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+        
+    return run_command('grep', ['-F'] + args, interp, env, stdin, stdout, 
+        stderr, debugflags)
+
+#-------------------------------------------------------------------------------  
+# gunzip utility
+#-------------------------------------------------------------------------------
+# see egrep
+def utility_gunzip(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+        
+    return run_command('gzip', ['-d'] + args, interp, env, stdin, stdout, 
+        stderr, debugflags)
+    
+#-------------------------------------------------------------------------------  
+# kill utility
+#-------------------------------------------------------------------------------
+def utility_kill(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+        
+    for arg in args:
+        pid = int(arg)
+        status = subprocess.call(['pskill', '/T', str(pid)],
+                                shell=True,
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.PIPE)
+        # pskill is asynchronous, hence the stupid polling loop
+        while 1:
+            p = subprocess.Popen(['pslist', str(pid)],
+                                shell=True,
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.STDOUT)
+            output = p.communicate()[0]
+            if ('process %d was not' % pid) in output:
+                break
+            time.sleep(1)
+    return status
+    
+#-------------------------------------------------------------------------------  
+# mkdir utility
+#-------------------------------------------------------------------------------
+OPT_MKDIR = NonExitingParser("mkdir - make directories.")
+OPT_MKDIR.add_option('-p', action='store_true', dest='has_p', default=False)
+
+def utility_mkdir(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+        
+    # TODO: implement umask
+    # TODO: implement proper utility error report
+    option, args = OPT_MKDIR.parse_args(args)
+    for arg in args:
+        path = os.path.join(env['PWD'], arg)
+        if option.has_p:
+            try:
+                os.makedirs(path)
+            except IOError as e:
+                if e.errno != errno.EEXIST:
+                    raise
+        else:               
+            os.mkdir(path)
+    return 0
+
+#-------------------------------------------------------------------------------  
+# netstat utility
+#-------------------------------------------------------------------------------
+def utility_netstat(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    # Do you really expect me to implement netstat ?
+    # This empty form is enough for Mercurial tests since it's
+    # supposed to generate nothing upon success. Faking this test
+    # is not a big deal either.
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+    return 0
+    
+#-------------------------------------------------------------------------------  
+# pwd utility
+#-------------------------------------------------------------------------------  
+OPT_PWD = NonExitingParser("pwd - return working directory name")
+OPT_PWD.add_option('-L', action='store_true', dest='has_L', default=True,
+    help="""If the PWD environment variable contains an absolute pathname of \
+    the current directory that does not contain the filenames dot or dot-dot, \
+    pwd shall write this pathname to standard output. Otherwise, the -L option \
+    shall behave as the -P option.""")
+OPT_PWD.add_option('-P', action='store_true', dest='has_L', default=False,
+    help="""The absolute pathname written shall not contain filenames that, in \
+    the context of the pathname, refer to files of type symbolic link.""")
+
+def utility_pwd(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+
+    option, args = OPT_PWD.parse_args(args)        
+    stdout.write('%s\n' % env['PWD'])
+    return 0
+    
+#-------------------------------------------------------------------------------  
+# printf utility
+#-------------------------------------------------------------------------------
+RE_UNESCAPE = re.compile(r'(\\x[a-zA-Z0-9]{2}|\\[0-7]{1,3}|\\.)')
+
+def utility_printf(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+        
+    def replace(m):
+        assert m.group()
+        g = m.group()[1:]
+        if g.startswith('x'):
+            return chr(int(g[1:], 16))
+        if len(g) <= 3 and len([c for c in g if c in '01234567']) == len(g):
+            # Yay, an octal number
+            return chr(int(g, 8))
+        return {
+            'a': '\a',
+            'b': '\b',
+            'f': '\f',
+            'n': '\n',
+            'r': '\r',
+            't': '\t',
+            'v': '\v',
+            '\\': '\\',
+        }.get(g)
+        
+    # Convert escape sequences
+    format = re.sub(RE_UNESCAPE, replace, args[0])
+    stdout.write(format % tuple(args[1:]))
+    return 0
+    
+#-------------------------------------------------------------------------------  
+# true utility
+#-------------------------------------------------------------------------------
+def utility_true(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+    return 0
+
+#-------------------------------------------------------------------------------  
+# sed utility
+#-------------------------------------------------------------------------------
+RE_SED = re.compile(r'^s(.).*\1[a-zA-Z]*$')
+
+# cygwin sed fails with some expressions when they do not end with a single space.
+# see unit tests for details. Interestingly, the same expressions works perfectly
+# in cygwin shell.
+def utility_sed(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+        
+    # Scan pattern arguments and append a space if necessary
+    for i in xrange(len(args)):
+        if not RE_SED.search(args[i]):
+            continue
+        args[i] = args[i] + ' '
+
+    return run_command(name, args, interp, env, stdin, stdout, 
+        stderr, debugflags)
+
+#-------------------------------------------------------------------------------  
+# sleep utility
+#-------------------------------------------------------------------------------
+def utility_sleep(name, args, interp, env, stdin, stdout, stderr, debugflags):
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+    time.sleep(int(args[0]))
+    return 0
+    
+#-------------------------------------------------------------------------------  
+# sort utility
+#-------------------------------------------------------------------------------
+OPT_SORT = NonExitingParser("sort - sort, merge, or sequence check text files")
+
+def utility_sort(name, args, interp, env, stdin, stdout, stderr, debugflags):
+
+    def sort(path):
+        if path == '-':
+            lines = stdin.readlines()
+        else:
+            try:
+                f = file(path)
+                try:
+                    lines = f.readlines()
+                finally:
+                    f.close()
+            except IOError as e:
+                stderr.write(str(e) + '\n')
+                return 1
+        
+        if lines and lines[-1][-1]!='\n':
+            lines[-1] = lines[-1] + '\n'
+        return lines
+    
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+        
+    option, args = OPT_SORT.parse_args(args)
+    alllines = []
+    
+    if len(args)<=0:
+        args += ['-']
+        
+    # Load all files lines
+    curdir = os.getcwd()
+    try:
+        os.chdir(env['PWD'])
+        for path in args:
+            alllines += sort(path)
+    finally:
+        os.chdir(curdir)
+            
+    alllines.sort()
+    for line in alllines:
+        stdout.write(line)
+    return 0
+    
+#-------------------------------------------------------------------------------
+# hg utility
+#-------------------------------------------------------------------------------
+
+hgcommands = [
+    'add',
+    'addremove',
+    'commit', 'ci',
+    'debugrename',
+    'debugwalk',
+    'falabala', # Dummy command used in a mercurial test
+    'incoming',
+    'locate',
+    'pull',
+    'push',
+    'qinit',
+    'remove', 'rm',
+    'rename', 'mv',
+    'revert',    
+    'showconfig',
+    'status', 'st',
+    'strip',
+    ]
+
+def rewriteslashes(name, args):
+    # Several hg commands output file paths, rewrite the separators
+    if len(args) > 1 and name.lower().endswith('python') \
+       and args[0].endswith('hg'):
+        for cmd in hgcommands:
+            if cmd in args[1:]:
+                return True
+            
+    # svn output contains many paths with OS specific separators.
+    # Normalize these to unix paths.
+    base = os.path.basename(name)
+    if base.startswith('svn'):
+        return True
+    
+    return False
+
+def rewritehg(output):
+    if not output:
+        return output
+    # Rewrite os specific messages
+    output = output.replace(': The system cannot find the file specified',
+                            ': No such file or directory')
+    output = re.sub(': Access is denied.*$', ': Permission denied', output)
+    output = output.replace(': No connection could be made because the target machine actively refused it',
+                            ': Connection refused')
+    return output
+                            
+
+def run_command(name, args, interp, env, stdin, stdout,
+                stderr, debugflags):
+    # Execute the command
+    if 'debug-utility' in debugflags:
+        print interp.log(' '.join([name, str(args), interp['PWD']]) + '\n')
+
+    hgbin = interp.options().hgbinary
+    ishg = hgbin and ('hg' in name or args and 'hg' in args[0])
+    unixoutput = 'cygwin' in name or ishg
+    
+    exec_env = env.get_variables()        
+    try:
+        # BUG: comparing file descriptor is clearly not a reliable way to tell
+        # whether they point on the same underlying object. But in pysh limited
+        # scope this is usually right, we do not expect complicated redirections
+        # besides usual 2>&1.
+        # Still there is one case we have but cannot deal with is when stdout
+        # and stderr are redirected *by pysh caller*. This the reason for the
+        # --redirect pysh() option.
+        # Now, we want to know they are the same because we sometimes need to 
+        # transform the command output, mostly remove CR-LF to ensure that
+        # command output is unix-like. Cygwin utilies are a special case because
+        # they explicitely set their output streams to binary mode, so we have
+        # nothing to do. For all others commands, we have to guess whether they
+        # are sending text data, in which case the transformation must be done.
+        # Again, the NUL character test is unreliable but should be enough for
+        # hg tests.
+        redirected = stdout.fileno()==stderr.fileno()
+        if not redirected:
+            p = subprocess.Popen([name] + args, cwd=env['PWD'], env=exec_env, 
+                    stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        else:
+            p = subprocess.Popen([name] + args, cwd=env['PWD'], env=exec_env, 
+                    stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        out, err = p.communicate()
+    except WindowsError as e:
+        raise UtilityError(str(e))
+
+    if not unixoutput:
+        def encode(s):
+            if '\0' in s:
+                return s
+            return s.replace('\r\n', '\n')
+    else:
+        encode = lambda s: s
+
+    if rewriteslashes(name, args):
+        encode1_ = encode
+        def encode(s):
+            s = encode1_(s)
+            s = s.replace('\\\\', '\\')
+            s = s.replace('\\', '/')
+            return s
+
+    if ishg:
+        encode2_ = encode
+        def encode(s):
+            return rewritehg(encode2_(s))
+    
+    stdout.write(encode(out))
+    if not redirected:
+        stderr.write(encode(err))
+    return p.returncode
+            
diff --git a/bitbake/lib/bb/pysh/interp.py b/bitbake/lib/bb/pysh/interp.py
new file mode 100644
index 0000000..25d8c92
--- /dev/null
+++ b/bitbake/lib/bb/pysh/interp.py
@@ -0,0 +1,1367 @@
+# interp.py - shell interpreter for pysh.
+#
+# Copyright 2007 Patrick Mezard
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+"""Implement the shell interpreter.
+
+Most references are made to "The Open Group Base Specifications Issue 6".
+<http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html>
+"""
+# TODO: document the fact input streams must implement fileno() so Popen will work correctly.
+# it requires non-stdin stream to be implemented as files. Still to be tested...
+# DOC: pathsep is used in PATH instead of ':'. Clearly, there are path syntax issues here.
+# TODO: stop command execution upon error.
+# TODO: sort out the filename/io_number mess. It should be possible to use filenames only.
+# TODO: review subshell implementation
+# TODO: test environment cloning for non-special builtins
+# TODO: set -x should not rebuild commands from tokens, assignments/redirections are lost
+# TODO: unit test for variable assignment
+# TODO: test error management wrt error type/utility type
+# TODO: test for binary output everywhere
+# BUG: debug-parsing does not pass log file to PLY. Maybe a PLY upgrade is necessary.
+import base64
+import cPickle as pickle
+import errno
+import glob
+import os
+import re
+import subprocess
+import sys
+import tempfile
+
+try:
+    s = set()
+    del s
+except NameError:
+    from Set import Set as set
+
+import builtin
+from sherrors import *
+import pyshlex
+import pyshyacc
+
+def mappend(func, *args, **kargs):
+    """Like map but assume func returns a list. Returned lists are merged into
+    a single one.
+    """
+    return reduce(lambda a,b: a+b, map(func, *args, **kargs), [])
+
+class FileWrapper:
+    """File object wrapper to ease debugging.
+    
+    Allow mode checking and implement file duplication through a simple 
+    reference counting scheme. Not sure the latter is really useful since
+    only real file descriptors can be used.
+    """
+    def __init__(self, mode, file, close=True):
+        if mode not in ('r', 'w', 'a'):
+            raise IOError('invalid mode: %s' % mode)
+        self._mode = mode
+        self._close = close
+        if isinstance(file, FileWrapper):
+            if file._refcount[0] <= 0:
+                raise IOError(0, 'Error')
+            self._refcount = file._refcount
+            self._refcount[0] += 1
+            self._file = file._file
+        else:
+            self._refcount = [1]
+            self._file = file
+        
+    def dup(self):
+        return FileWrapper(self._mode, self, self._close)
+        
+    def fileno(self):
+        """fileno() should be only necessary for input streams."""
+        return self._file.fileno()
+        
+    def read(self, size=-1):
+        if self._mode!='r':
+            raise IOError(0, 'Error')
+        return self._file.read(size)
+        
+    def readlines(self, *args, **kwargs):
+        return self._file.readlines(*args, **kwargs)
+        
+    def write(self, s):
+        if self._mode not in ('w', 'a'):
+            raise IOError(0, 'Error')
+        return self._file.write(s)
+        
+    def flush(self):
+        self._file.flush()
+        
+    def close(self):        
+        if not self._refcount:
+            return
+        assert  self._refcount[0] > 0
+        
+        self._refcount[0] -= 1    
+        if self._refcount[0] == 0:
+            self._mode = 'c'
+            if self._close:
+                self._file.close()
+        self._refcount = None
+                
+    def mode(self):
+        return self._mode
+
+    def __getattr__(self, name):
+        if name == 'name':
+            self.name = getattr(self._file, name)
+            return self.name
+        else:
+            raise AttributeError(name)
+
+    def __del__(self):
+        self.close()
+               
+               
+def win32_open_devnull(mode):
+    return open('NUL', mode)
+    
+        
+class Redirections:
+    """Stores open files and their mapping to pseudo-sh file descriptor.
+    """
+    # BUG: redirections are not handled correctly: 1>&3 2>&3 3>&4 does 
+    # not make 1 to redirect to 4
+    def __init__(self, stdin=None, stdout=None, stderr=None):
+        self._descriptors = {}
+        if stdin is not None:
+            self._add_descriptor(0, stdin)
+        if stdout is not None:
+            self._add_descriptor(1, stdout)
+        if stderr is not None:
+            self._add_descriptor(2, stderr)
+            
+    def add_here_document(self, interp, name, content, io_number=None):
+        if io_number is None:
+            io_number = 0
+            
+        if name==pyshlex.unquote_wordtree(name):
+            content = interp.expand_here_document(('TOKEN', content))
+    
+        # Write document content in a temporary file
+        tmp = tempfile.TemporaryFile()
+        try:
+            tmp.write(content)
+            tmp.flush()
+            tmp.seek(0)
+            self._add_descriptor(io_number, FileWrapper('r', tmp))
+        except:
+            tmp.close()
+            raise                
+        
+    def add(self, interp, op, filename, io_number=None):
+        if op not in ('<', '>', '>|', '>>', '>&'):
+            # TODO: add descriptor duplication and here_documents      
+            raise RedirectionError('Unsupported redirection operator "%s"' % op)
+            
+        if io_number is not None:
+            io_number = int(io_number)
+            
+        if (op == '>&' and filename.isdigit()) or filename=='-':
+            # No expansion for file descriptors, quote them if you want a filename
+            fullname = filename
+        else:
+            if filename.startswith('/'):
+                # TODO: win32 kludge
+                if filename=='/dev/null':
+                    fullname = 'NUL'
+                else:
+                    # TODO: handle absolute pathnames, they are unlikely to exist on the
+                    # current platform (win32 for instance).
+                    raise NotImplementedError()
+            else:
+                fullname = interp.expand_redirection(('TOKEN', filename))
+                if not fullname:
+                    raise RedirectionError('%s: ambiguous redirect' % filename)
+                # Build absolute path based on PWD
+                fullname = os.path.join(interp.get_env()['PWD'], fullname)
+            
+        if op=='<':
+            return self._add_input_redirection(interp, fullname, io_number)
+        elif op in ('>', '>|'):
+            clobber = ('>|'==op)
+            return self._add_output_redirection(interp, fullname, io_number, clobber)
+        elif op=='>>':
+            return self._add_output_appending(interp, fullname, io_number)
+        elif op=='>&':
+            return self._dup_output_descriptor(fullname, io_number)
+        
+    def close(self):
+        if self._descriptors is not None:
+            for desc in self._descriptors.itervalues():
+                desc.flush()
+                desc.close()
+            self._descriptors = None
+            
+    def stdin(self):
+        return self._descriptors[0]
+          
+    def stdout(self):
+        return self._descriptors[1] 
+        
+    def stderr(self):
+        return self._descriptors[2] 
+            
+    def clone(self):
+        clone = Redirections()
+        for desc, fileobj in self._descriptors.iteritems():
+            clone._descriptors[desc] = fileobj.dup()
+        return clone
+           
+    def _add_output_redirection(self, interp, filename, io_number, clobber):    
+        if io_number is None:
+            # io_number default to standard output
+            io_number = 1
+        
+        if not clobber and interp.get_env().has_opt('-C') and os.path.isfile(filename):
+            # File already exist in no-clobber mode, bail out
+            raise RedirectionError('File "%s" already exists' % filename)
+            
+        # Open and register
+        self._add_file_descriptor(io_number, filename, 'w')
+        
+    def _add_output_appending(self, interp, filename, io_number):    
+        if io_number is None:
+            io_number = 1
+        self._add_file_descriptor(io_number, filename, 'a')
+            
+    def _add_input_redirection(self, interp, filename, io_number):
+        if io_number is None:
+            io_number = 0
+        self._add_file_descriptor(io_number, filename, 'r')
+        
+    def _add_file_descriptor(self, io_number, filename, mode):    
+        try:            
+            if filename.startswith('/'):
+                if filename=='/dev/null':
+                    f = win32_open_devnull(mode+'b')
+                else:
+                    # TODO: handle absolute pathnames, they are unlikely to exist on the
+                    # current platform (win32 for instance).
+                    raise NotImplementedError('cannot open absolute path %s' % repr(filename))
+            else:
+                f = file(filename, mode+'b')
+        except IOError as e:
+            raise RedirectionError(str(e))
+            
+        wrapper = None
+        try:
+            wrapper = FileWrapper(mode, f)
+            f = None
+            self._add_descriptor(io_number, wrapper)
+        except:
+            if f: f.close()
+            if wrapper: wrapper.close()
+            raise
+            
+    def _dup_output_descriptor(self, source_fd, dest_fd):
+        if source_fd is None:
+            source_fd = 1
+        self._dup_file_descriptor(source_fd, dest_fd, 'w')
+            
+    def _dup_file_descriptor(self, source_fd, dest_fd, mode):
+        source_fd = int(source_fd)
+        if source_fd not in self._descriptors:
+            raise RedirectionError('"%s" is not a valid file descriptor' % str(source_fd))
+        source = self._descriptors[source_fd]
+        
+        if source.mode()!=mode:
+            raise RedirectionError('Descriptor %s cannot be duplicated in mode "%s"' % (str(source), mode))
+        
+        if dest_fd=='-':
+            # Close the source descriptor
+            del self._descriptors[source_fd]
+            source.close()
+        else:
+            dest_fd = int(dest_fd)
+            if dest_fd not in self._descriptors:
+                raise RedirectionError('Cannot replace file descriptor %s' % str(dest_fd))
+                
+            dest = self._descriptors[dest_fd]
+            if dest.mode()!=mode:
+                raise RedirectionError('Descriptor %s cannot be cannot be redirected in mode "%s"' % (str(dest), mode))
+            
+            self._descriptors[dest_fd] = source.dup()
+            dest.close()        
+            
+    def _add_descriptor(self, io_number, file):
+        io_number = int(io_number)
+        
+        if io_number in self._descriptors:
+            # Close the current descriptor
+            d = self._descriptors[io_number]
+            del self._descriptors[io_number]
+            d.close()
+            
+        self._descriptors[io_number] = file
+
+    def __str__(self):
+        names = [('%d=%r' % (k, getattr(v, 'name', None))) for k,v
+                 in self._descriptors.iteritems()]
+        names = ','.join(names)
+        return 'Redirections(%s)' % names
+
+    def __del__(self):
+        self.close()
+    
+def cygwin_to_windows_path(path):
+    """Turn /cygdrive/c/foo into c:/foo, or return path if it
+    is not a cygwin path.
+    """
+    if not path.startswith('/cygdrive/'):
+        return path
+    path = path[len('/cygdrive/'):]
+    path = path[:1] + ':' + path[1:]
+    return path
+    
+def win32_to_unix_path(path):
+    if path is not None:
+        path = path.replace('\\', '/')
+    return path    
+    
+_RE_SHEBANG = re.compile(r'^\#!\s?([^\s]+)(?:\s([^\s]+))?')
+_SHEBANG_CMDS = {
+    '/usr/bin/env': 'env',
+    '/bin/sh': 'pysh',
+    'python': 'python',
+}
+    
+def resolve_shebang(path, ignoreshell=False):
+    """Return a list of arguments as shebang interpreter call or an empty list
+    if path does not refer to an executable script.
+    See <http://www.opengroup.org/austin/docs/austin_51r2.txt>.
+    
+    ignoreshell - set to True to ignore sh shebangs. Return an empty list instead.
+    """
+    try:
+        f = file(path)
+        try:
+            # At most 80 characters in the first line
+            header = f.read(80).splitlines()[0]
+        finally:
+            f.close()
+            
+        m = _RE_SHEBANG.search(header)
+        if not m:
+            return []
+        cmd, arg = m.group(1,2)
+        if os.path.isfile(cmd):
+            # Keep this one, the hg script for instance contains a weird windows
+            # shebang referencing the current python install.
+            cmdfile = os.path.basename(cmd).lower()
+            if cmdfile == 'python.exe':
+                cmd = 'python'
+            pass
+        elif cmd not in _SHEBANG_CMDS:
+            raise CommandNotFound('Unknown interpreter "%s" referenced in '\
+                'shebang' % header)
+        cmd = _SHEBANG_CMDS.get(cmd)
+        if cmd is None or (ignoreshell and cmd == 'pysh'):
+            return []
+        if arg is None:
+            return [cmd, win32_to_unix_path(path)]
+        return [cmd, arg, win32_to_unix_path(path)]
+    except IOError as e:
+        if  e.errno!=errno.ENOENT and \
+            (e.errno!=errno.EPERM and not os.path.isdir(path)): # Opening a directory raises EPERM
+            raise
+        return []
+        
+def win32_find_in_path(name, path):
+    if isinstance(path, str):
+        path = path.split(os.pathsep)
+        
+    exts = os.environ.get('PATHEXT', '').lower().split(os.pathsep)
+    for p in path:
+        p_name = os.path.join(p, name)
+        
+        prefix = resolve_shebang(p_name)
+        if prefix:
+            return prefix
+            
+        for ext in exts:    
+            p_name_ext = p_name + ext
+            if os.path.exists(p_name_ext):
+                return [win32_to_unix_path(p_name_ext)]
+    return []
+
+class Traps(dict):
+    def __setitem__(self, key, value):
+        if key not in ('EXIT',):
+            raise NotImplementedError()
+        super(Traps, self).__setitem__(key, value)
+
+# IFS white spaces character class
+_IFS_WHITESPACES = (' ', '\t', '\n')
+
+class Environment:
+    """Environment holds environment variables, export table, function 
+    definitions and whatever is defined in 2.12 "Shell Execution Environment",
+    redirection excepted.
+    """
+    def __init__(self, pwd):
+        self._opt = set()       #Shell options
+        
+        self._functions = {}        
+        self._env = {'?': '0', '#': '0'}
+        self._exported = set([
+            'HOME', 'IFS', 'PATH'
+        ])
+        
+        # Set environment vars with side-effects
+        self._ifs_ws = None     # Set of IFS whitespace characters
+        self._ifs_re = None     # Regular expression used to split between words using IFS classes
+        self['IFS'] = ''.join(_IFS_WHITESPACES) #Default environment values
+        self['PWD'] = pwd
+        self.traps = Traps()
+        
+    def clone(self, subshell=False):
+        env = Environment(self['PWD'])
+        env._opt = set(self._opt)
+        for k,v in self.get_variables().iteritems():
+            if k in self._exported:
+                env.export(k,v)
+            elif subshell:
+                env[k] = v
+                
+        if subshell:
+            env._functions = dict(self._functions)
+            
+        return env        
+        
+    def __getitem__(self, key):
+        if key in ('@', '*', '-', '$'):
+            raise NotImplementedError('%s is not implemented' % repr(key))
+        return self._env[key]
+        
+    def get(self, key, defval=None):
+        try:
+            return self[key]
+        except KeyError:
+            return defval
+        
+    def __setitem__(self, key, value):
+        if key=='IFS':
+            # Update the whitespace/non-whitespace classes
+            self._update_ifs(value)
+        elif key=='PWD':
+            pwd = os.path.abspath(value)
+            if not os.path.isdir(pwd):
+                raise VarAssignmentError('Invalid directory %s' % value)
+            value = pwd
+        elif key in ('?', '!'):
+            value = str(int(value))
+        self._env[key] = value
+        
+    def __delitem__(self, key):
+        if key in ('IFS', 'PWD', '?'):
+            raise VarAssignmentError('%s cannot be unset' % key)
+        del self._env[key]
+
+    def __contains__(self, item):
+        return item in self._env
+        
+    def set_positional_args(self, args):
+        """Set the content of 'args' as positional argument from 1 to len(args).
+        Return previous argument as a list of strings.
+        """
+        # Save and remove previous arguments
+        prevargs = []        
+        for i in xrange(int(self._env['#'])):
+            i = str(i+1)
+            prevargs.append(self._env[i])
+            del self._env[i]
+        self._env['#'] = '0'
+                
+        #Set new ones
+        for i,arg in enumerate(args):
+            self._env[str(i+1)] = str(arg)
+        self._env['#'] = str(len(args))
+        
+        return prevargs
+        
+    def get_positional_args(self):
+        return [self._env[str(i+1)] for i in xrange(int(self._env['#']))]
+        
+    def get_variables(self):
+        return dict(self._env)
+        
+    def export(self, key, value=None):
+        if value is not None:
+            self[key] = value
+        self._exported.add(key)
+        
+    def get_exported(self):
+        return [(k,self._env.get(k)) for k in self._exported]
+            
+    def split_fields(self, word):
+        if not self._ifs_ws or not word:
+            return [word]
+        return re.split(self._ifs_re, word)
+   
+    def _update_ifs(self, value):
+        """Update the split_fields related variables when IFS character set is
+        changed.
+        """
+        # TODO: handle NULL IFS
+        
+        # Separate characters in whitespace and non-whitespace
+        chars = set(value)
+        ws = [c for c in chars if c in _IFS_WHITESPACES]
+        nws = [c for c in chars if c not in _IFS_WHITESPACES]
+        
+        # Keep whitespaces in a string for left and right stripping
+        self._ifs_ws = ''.join(ws)
+        
+        # Build a regexp to split fields
+        trailing = '[' + ''.join([re.escape(c) for c in ws]) + ']'
+        if nws:
+            # First, the single non-whitespace occurence.
+            nws = '[' + ''.join([re.escape(c) for c in nws]) + ']'
+            nws = '(?:' + trailing + '*' + nws + trailing + '*' + '|' + trailing + '+)'
+        else:
+            # Then mix all parts with quantifiers
+            nws = trailing + '+'
+        self._ifs_re = re.compile(nws)
+       
+    def has_opt(self, opt, val=None):
+        return (opt, val) in self._opt
+        
+    def set_opt(self, opt, val=None):
+        self._opt.add((opt, val))
+        
+    def find_in_path(self, name, pwd=False):
+        path = self._env.get('PATH', '').split(os.pathsep)
+        if pwd:
+            path[:0] = [self['PWD']]
+        if os.name == 'nt':
+            return win32_find_in_path(name, self._env.get('PATH', ''))
+        else:
+            raise NotImplementedError()
+            
+    def define_function(self, name, body):
+        if not is_name(name):
+            raise ShellSyntaxError('%s is not a valid function name' % repr(name))
+        self._functions[name] = body
+        
+    def remove_function(self, name):
+        del self._functions[name]
+        
+    def is_function(self, name):
+        return name in self._functions
+        
+    def get_function(self, name):
+        return self._functions.get(name)
+        
+       
+name_charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'
+name_charset = dict(zip(name_charset,name_charset))
+           
+def match_name(s):
+    """Return the length in characters of the longest prefix made of name
+    allowed characters in s.
+    """
+    for i,c in enumerate(s):
+        if c not in name_charset:
+            return s[:i]
+    return s
+    
+def is_name(s):
+    return len([c for c in s if c not in name_charset])<=0
+    
+def is_special_param(c):
+    return len(c)==1 and c in ('@','*','#','?','-','$','!','0')
+    
+def utility_not_implemented(name, *args, **kwargs):
+    raise NotImplementedError('%s utility is not implemented' % name)
+    
+
+class Utility:
+    """Define utilities properties:
+    func -- utility callable. See builtin module for utility samples.
+    is_special -- see XCU 2.8.
+    """
+    def __init__(self, func, is_special=0):
+        self.func = func
+        self.is_special = bool(is_special)
+
+
+def encodeargs(args):
+    def encodearg(s):
+        lines = base64.encodestring(s)
+        lines = [l.splitlines()[0] for l in lines]
+        return ''.join(lines)
+
+    s = pickle.dumps(args)
+    return encodearg(s)
+
+def decodeargs(s):
+    s = base64.decodestring(s)
+    return pickle.loads(s)
+    
+
+class GlobError(Exception):
+    pass
+
+class Options:
+    def __init__(self):
+        # True if Mercurial operates with binary streams
+        self.hgbinary = True
+
+class Interpreter:
+    # Implementation is very basic: the execute() method just makes a DFS on the
+    # AST and execute nodes one by one. Nodes are tuple (name,obj) where name
+    # is a string identifier and obj the AST element returned by the parser.
+    #
+    # Handler are named after the node identifiers.
+    # TODO: check node names and remove the switch in execute with some
+    # dynamic getattr() call to find node handlers.
+    """Shell interpreter.
+    
+    The following debugging flags can be passed:
+    debug-parsing - enable PLY debugging.
+    debug-tree - print the generated AST.
+    debug-cmd - trace command execution before word expansion, plus exit status.
+    debug-utility - trace utility execution.
+    """
+    
+    # List supported commands.
+    COMMANDS = {
+        'cat':       Utility(builtin.utility_cat,),
+        'cd':       Utility(builtin.utility_cd,),
+        ':':        Utility(builtin.utility_colon,),
+        'echo':     Utility(builtin.utility_echo),
+        'env':      Utility(builtin.utility_env),
+        'exit':     Utility(builtin.utility_exit),
+        'export':   Utility(builtin.builtin_export,     is_special=1),
+        'egrep':    Utility(builtin.utility_egrep),
+        'fgrep':    Utility(builtin.utility_fgrep),
+        'gunzip':   Utility(builtin.utility_gunzip),
+        'kill':     Utility(builtin.utility_kill),
+        'mkdir':    Utility(builtin.utility_mkdir),
+        'netstat':  Utility(builtin.utility_netstat),
+        'printf':   Utility(builtin.utility_printf),
+        'pwd':      Utility(builtin.utility_pwd),
+        'return':   Utility(builtin.builtin_return,     is_special=1),
+        'sed':      Utility(builtin.utility_sed,),
+        'set':      Utility(builtin.builtin_set,),
+        'shift':    Utility(builtin.builtin_shift,),
+        'sleep':    Utility(builtin.utility_sleep,),
+        'sort':     Utility(builtin.utility_sort,),
+        'trap':     Utility(builtin.builtin_trap,       is_special=1),
+        'true':     Utility(builtin.utility_true),
+        'unset':    Utility(builtin.builtin_unset,      is_special=1),
+        'wait':     Utility(builtin.builtin_wait,       is_special=1),
+    }
+    
+    def __init__(self, pwd, debugflags = [], env=None, redirs=None, stdin=None,
+                 stdout=None, stderr=None, opts=Options()):
+        self._env = env
+        if self._env is None:
+            self._env = Environment(pwd)
+        self._children = {}
+            
+        self._redirs = redirs
+        self._close_redirs = False
+        
+        if self._redirs is None:
+            if stdin is None:
+                stdin = sys.stdin
+            if stdout is None:
+                stdout = sys.stdout
+            if stderr is None:
+                stderr = sys.stderr
+            stdin = FileWrapper('r', stdin, False)
+            stdout = FileWrapper('w', stdout, False)
+            stderr = FileWrapper('w', stderr, False)
+            self._redirs = Redirections(stdin, stdout, stderr)
+            self._close_redirs = True
+            
+        self._debugflags = list(debugflags)
+        self._logfile = sys.stderr
+        self._options = opts
+        
+    def close(self):
+        """Must be called when the interpreter is no longer used."""
+        script = self._env.traps.get('EXIT')
+        if script:
+            try:
+                self.execute_script(script=script)
+            except:
+                pass
+
+        if self._redirs is not None and self._close_redirs:
+            self._redirs.close()
+            self._redirs = None
+            
+    def log(self, s):
+        self._logfile.write(s)
+        self._logfile.flush()
+            
+    def __getitem__(self, key):
+        return self._env[key]
+        
+    def __setitem__(self, key, value):
+        self._env[key] = value
+
+    def options(self):
+        return self._options
+
+    def redirect(self, redirs, ios):
+        def add_redir(io):
+            if isinstance(io, pyshyacc.IORedirect):
+                redirs.add(self, io.op, io.filename, io.io_number)
+            else:
+                redirs.add_here_document(self, io.name, io.content, io.io_number)
+                    
+        map(add_redir, ios)
+        return redirs
+            
+    def execute_script(self, script=None, ast=None, sourced=False,
+                       scriptpath=None):
+        """If script is not None, parse the input. Otherwise takes the supplied
+        AST. Then execute the AST.
+        Return the script exit status.
+        """
+        try:
+            if scriptpath is not None:
+                self._env['0'] = os.path.abspath(scriptpath)
+
+            if script is not None:
+                debug_parsing = ('debug-parsing' in self._debugflags)    
+                cmds, script = pyshyacc.parse(script, True, debug_parsing)
+                if 'debug-tree' in self._debugflags:
+                    pyshyacc.print_commands(cmds, self._logfile)
+                    self._logfile.flush()
+            else:
+                cmds, script = ast, ''                
+                
+            status = 0
+            for cmd in cmds:
+                try:
+                    status = self.execute(cmd)
+                except ExitSignal as e:
+                    if sourced:
+                        raise
+                    status = int(e.args[0])
+                    return status
+                except ShellError:
+                    self._env['?'] = 1
+                    raise
+                if 'debug-utility' in self._debugflags or 'debug-cmd' in self._debugflags:
+                    self.log('returncode ' + str(status)+ '\n')
+            return status
+        except CommandNotFound as e:
+            print >>self._redirs.stderr, str(e)
+            self._redirs.stderr.flush()
+            # Command not found by non-interactive shell
+            # return 127
+            raise
+        except RedirectionError as e:
+            # TODO: should be handled depending on the utility status
+            print >>self._redirs.stderr, str(e)
+            self._redirs.stderr.flush()
+            # Command not found by non-interactive shell
+            # return 127
+            raise
+
+    def dotcommand(self, env, args):
+        if len(args) < 1:
+            raise ShellError('. expects at least one argument')
+        path = args[0]
+        if '/' not in path:
+            found = env.find_in_path(args[0], True)
+            if found:
+                path = found[0]
+        script = file(path).read()
+        return self.execute_script(script=script, sourced=True)
+
+    def execute(self, token, redirs=None):
+        """Execute and AST subtree with supplied redirections overriding default
+        interpreter ones.
+        Return the exit status.
+        """
+        if not token:
+            return 0
+            
+        if redirs is None:
+            redirs = self._redirs
+            
+        if isinstance(token, list):
+            # Commands sequence
+            res = 0
+            for t in token:
+                res = self.execute(t, redirs)
+            return res
+
+        type, value = token
+        status = 0
+        if type=='simple_command':
+            redirs_copy = redirs.clone()
+            try:
+                # TODO: define and handle command return values
+                # TODO: implement set -e
+                status = self._execute_simple_command(value, redirs_copy)
+            finally:
+                redirs_copy.close()
+        elif type=='pipeline':
+            status = self._execute_pipeline(value, redirs)
+        elif type=='and_or':
+            status = self._execute_and_or(value, redirs)
+        elif type=='for_clause':
+            status = self._execute_for_clause(value, redirs)
+        elif type=='while_clause':
+            status = self._execute_while_clause(value, redirs)
+        elif type=='function_definition':
+            status = self._execute_function_definition(value, redirs)
+        elif type=='brace_group':
+            status = self._execute_brace_group(value, redirs)
+        elif type=='if_clause':
+            status = self._execute_if_clause(value, redirs)
+        elif type=='subshell':
+            status = self.subshell(ast=value.cmds, redirs=redirs)
+        elif type=='async':
+            status = self._asynclist(value)
+        elif type=='redirect_list':
+            redirs_copy = self.redirect(redirs.clone(), value.redirs)
+            try:
+                status = self.execute(value.cmd, redirs_copy)
+            finally:
+                redirs_copy.close()
+        else:
+            raise NotImplementedError('Unsupported token type ' + type)
+
+        if status < 0:
+            status = 255
+        return status
+            
+    def _execute_if_clause(self, if_clause, redirs):
+        cond_status = self.execute(if_clause.cond, redirs)
+        if cond_status==0:
+            return self.execute(if_clause.if_cmds, redirs)
+        else:
+            return self.execute(if_clause.else_cmds, redirs)
+      
+    def _execute_brace_group(self, group, redirs):
+        status = 0
+        for cmd in group.cmds:
+            status = self.execute(cmd, redirs)
+        return status
+            
+    def _execute_function_definition(self, fundef, redirs):
+        self._env.define_function(fundef.name, fundef.body)
+        return 0
+            
+    def _execute_while_clause(self, while_clause, redirs):
+        status = 0
+        while 1:
+            cond_status = 0
+            for cond in while_clause.condition:
+                cond_status = self.execute(cond, redirs)
+                
+            if cond_status:
+                break
+                
+            for cmd in while_clause.cmds:
+                status = self.execute(cmd, redirs)
+                
+        return status
+            
+    def _execute_for_clause(self, for_clause, redirs):
+        if not is_name(for_clause.name):
+            raise ShellSyntaxError('%s is not a valid name' % repr(for_clause.name))
+        items = mappend(self.expand_token, for_clause.items)
+        
+        status = 0    
+        for item in items:
+            self._env[for_clause.name] = item
+            for cmd in for_clause.cmds:
+                status = self.execute(cmd, redirs)
+        return status
+            
+    def _execute_and_or(self, or_and, redirs):
+        res = self.execute(or_and.left, redirs)        
+        if (or_and.op=='&&' and res==0) or (or_and.op!='&&' and res!=0):
+            res = self.execute(or_and.right, redirs)
+        return res
+            
+    def _execute_pipeline(self, pipeline, redirs):            
+        if len(pipeline.commands)==1:
+            status = self.execute(pipeline.commands[0], redirs)
+        else:
+            # Execute all commands one after the other
+            status = 0
+            inpath, outpath = None, None
+            try:
+                # Commands inputs and outputs cannot really be plugged as done
+                # by a real shell. Run commands sequentially and chain their
+                # input/output throught temporary files.
+                tmpfd, inpath = tempfile.mkstemp()
+                os.close(tmpfd)
+                tmpfd, outpath = tempfile.mkstemp()
+                os.close(tmpfd)
+                
+                inpath = win32_to_unix_path(inpath)
+                outpath = win32_to_unix_path(outpath)
+                
+                for i, cmd in enumerate(pipeline.commands):
+                    call_redirs = redirs.clone()
+                    try:
+                        if i!=0:
+                            call_redirs.add(self, '<', inpath)
+                        if i!=len(pipeline.commands)-1:
+                            call_redirs.add(self, '>', outpath)
+                        
+                        status = self.execute(cmd, call_redirs)
+                        
+                        # Chain inputs/outputs
+                        inpath, outpath = outpath, inpath
+                    finally:
+                        call_redirs.close()            
+            finally:
+                if inpath: os.remove(inpath)
+                if outpath: os.remove(outpath)
+        
+        if pipeline.reverse_status:
+            status = int(not status)
+        self._env['?'] = status
+        return status
+        
+    def _execute_function(self, name, args, interp, env, stdin, stdout, stderr, *others):
+        assert interp is self
+        
+        func = env.get_function(name)
+        #Set positional parameters
+        prevargs = None
+        try:
+            prevargs = env.set_positional_args(args)
+            try:
+                redirs = Redirections(stdin.dup(), stdout.dup(), stderr.dup())
+                try:
+                    status = self.execute(func, redirs)
+                finally:
+                    redirs.close()
+            except ReturnSignal as e:
+                status = int(e.args[0])
+                env['?'] = status
+            return status
+        finally:
+            #Reset positional parameters
+            if prevargs is not None:
+                env.set_positional_args(prevargs)
+                
+    def _execute_simple_command(self, token, redirs):
+        """Can raise ReturnSignal when return builtin is called, ExitSignal when
+        exit is called, and other shell exceptions upon builtin failures.
+        """
+        debug_command = 'debug-cmd' in self._debugflags
+        if debug_command:
+            self.log('word' + repr(token.words) + '\n')
+            self.log('assigns' + repr(token.assigns) + '\n')
+            self.log('redirs' + repr(token.redirs) + '\n')
+        
+        is_special = None
+        env = self._env
+        
+        try:
+            # Word expansion
+            args = []
+            for word in token.words:                
+                args += self.expand_token(word)
+                if is_special is None and args:
+                    is_special = env.is_function(args[0]) or \
+                        (args[0] in self.COMMANDS and self.COMMANDS[args[0]].is_special)
+                        
+            if debug_command:
+                self.log('_execute_simple_command' + str(args) + '\n')
+                
+            if not args:
+                # Redirections happen is a subshell
+                redirs = redirs.clone()
+            elif not is_special:
+                env = self._env.clone()
+            
+            # Redirections
+            self.redirect(redirs, token.redirs)
+                
+            # Variables assignments
+            res = 0
+            for type,(k,v) in token.assigns:
+                status, expanded = self.expand_variable((k,v))
+                if status is not None:
+                    res = status
+                if args:
+                    env.export(k, expanded)
+                else:
+                    env[k] = expanded
+                
+            if args and args[0] in ('.', 'source'):
+                res = self.dotcommand(env, args[1:])
+            elif args:
+                if args[0] in self.COMMANDS:
+                    command = self.COMMANDS[args[0]]
+                elif env.is_function(args[0]):
+                    command = Utility(self._execute_function, is_special=True)
+                else:
+                    if not '/' in args[0].replace('\\', '/'):
+                        cmd = env.find_in_path(args[0])
+                        if not cmd:
+                            # TODO: test error code on unknown command => 127
+                            raise CommandNotFound('Unknown command: "%s"' % args[0])
+                    else:
+                        # Handle commands like '/cygdrive/c/foo.bat'
+                        cmd = cygwin_to_windows_path(args[0])
+                        if not os.path.exists(cmd):
+                            raise CommandNotFound('%s: No such file or directory' % args[0])
+                        shebang = resolve_shebang(cmd)
+                        if shebang:
+                            cmd = shebang
+                        else:
+                            cmd = [cmd]
+                    args[0:1] = cmd
+                    command = Utility(builtin.run_command)
+                
+                # Command execution
+                if 'debug-cmd' in self._debugflags:
+                    self.log('redirections ' + str(redirs) + '\n')
+                    
+                res = command.func(args[0], args[1:], self, env,
+                                   redirs.stdin(), redirs.stdout(), 
+                                   redirs.stderr(), self._debugflags)
+            
+            if self._env.has_opt('-x'):
+                # Trace command execution in shell environment
+                # BUG: would be hard to reproduce a real shell behaviour since
+                # the AST is not annotated with source lines/tokens.
+                self._redirs.stdout().write(' '.join(args))
+                
+        except ReturnSignal:
+            raise
+        except ShellError as e:
+            if is_special or isinstance(e, (ExitSignal,
+                                            ShellSyntaxError, ExpansionError)):
+                raise e
+            self._redirs.stderr().write(str(e)+'\n')
+            return 1
+
+        return res
+
+    def expand_token(self, word):
+        """Expand a word as specified in [2.6 Word Expansions]. Return the list
+        of expanded words.
+        """
+        status, wtrees = self._expand_word(word)
+        return map(pyshlex.wordtree_as_string, wtrees)
+        
+    def expand_variable(self, word):
+        """Return a status code (or None if no command expansion occurred)
+        and a single word.
+        """
+        status, wtrees = self._expand_word(word, pathname=False, split=False)
+        words = map(pyshlex.wordtree_as_string, wtrees)
+        assert len(words)==1
+        return status, words[0]
+        
+    def expand_here_document(self, word):
+        """Return the expanded document as a single word. The here document is 
+        assumed to be unquoted.
+        """
+        status, wtrees = self._expand_word(word, pathname=False,
+                                           split=False, here_document=True)
+        words = map(pyshlex.wordtree_as_string, wtrees)
+        assert len(words)==1
+        return words[0]
+        
+    def expand_redirection(self, word):
+        """Return a single word."""
+        return self.expand_variable(word)[1]
+        
+    def get_env(self):
+        return self._env
+        
+    def _expand_word(self, token, pathname=True, split=True, here_document=False):
+        wtree = pyshlex.make_wordtree(token[1], here_document=here_document)
+        
+        # TODO: implement tilde expansion
+        def expand(wtree):
+            """Return a pseudo wordtree: the tree or its subelements can be empty
+            lists when no value result from the expansion.
+            """
+            status = None
+            for part in wtree:
+                if not isinstance(part, list):
+                    continue
+                if part[0]in ("'", '\\'):
+                    continue
+                elif part[0] in ('`', '$('):
+                    status, result = self._expand_command(part)
+                    part[:] = result
+                elif part[0] in ('$', '${'):
+                    part[:] = self._expand_parameter(part, wtree[0]=='"', split)
+                elif part[0] in ('', '"'):
+                    status, result = expand(part)
+                    part[:] = result
+                else:
+                    raise NotImplementedError('%s expansion is not implemented'
+                                              % part[0])
+            # [] is returned when an expansion result in no-field,
+            # like an empty $@
+            wtree = [p for p in wtree if p != []]
+            if len(wtree) < 3:
+                return status, []
+            return status, wtree
+        
+        status, wtree = expand(wtree)
+        if len(wtree) == 0:
+            return status, wtree
+        wtree = pyshlex.normalize_wordtree(wtree)
+        
+        if split:
+            wtrees = self._split_fields(wtree)
+        else:
+            wtrees = [wtree]
+        
+        if pathname:
+            wtrees = mappend(self._expand_pathname, wtrees)
+        
+        wtrees = map(self._remove_quotes, wtrees)
+        return status, wtrees
+        
+    def _expand_command(self, wtree):
+        # BUG: there is something to do with backslashes and quoted
+        # characters here
+        command = pyshlex.wordtree_as_string(wtree[1:-1])
+        status, output = self.subshell_output(command)
+        return status, ['', output, '']
+        
+    def _expand_parameter(self, wtree, quoted=False, split=False):
+        """Return a valid wtree or an empty list when no parameter results."""
+        # Get the parameter name
+        # TODO: implement weird expansion rules with ':'
+        name = pyshlex.wordtree_as_string(wtree[1:-1])
+        if not is_name(name) and not is_special_param(name):
+            raise ExpansionError('Bad substitution "%s"' % name)
+        # TODO: implement special parameters
+        if name in ('@', '*'):
+            args = self._env.get_positional_args()
+            if len(args) == 0:
+                return []
+            if len(args)<2:
+                return ['', ''.join(args), '']
+                
+            sep = self._env.get('IFS', '')[:1]
+            if split and quoted and name=='@':
+                # Introduce a new token to tell the caller that these parameters
+                # cause a split as specified in 2.5.2
+                return ['@'] + args + ['']
+            else:
+                return ['', sep.join(args), '']                
+        
+        return ['', self._env.get(name, ''), '']
+        
+    def _split_fields(self, wtree):
+        def is_empty(split):
+            return split==['', '', '']
+            
+        def split_positional(quoted):
+            # Return a list of wtree split according positional parameters rules.
+            # All remaining '@' groups are removed.
+            assert quoted[0]=='"'
+            
+            splits = [[]]
+            for part in quoted:
+                if not isinstance(part, list) or part[0]!='@':
+                    splits[-1].append(part)
+                else:
+                    # Empty or single argument list were dealt with already
+                    assert len(part)>3
+                    # First argument must join with the beginning part of the original word
+                    splits[-1].append(part[1])
+                    # Create double-quotes expressions for every argument after the first
+                    for arg in part[2:-1]:
+                        splits[-1].append('"')
+                        splits.append(['"', arg])
+            return splits
+        
+        # At this point, all expansions but pathnames have occured. Only quoted
+        # and positional sequences remain. Thus, all candidates for field splitting 
+        # are in the tree root, or are positional splits ('@') and lie in root
+        # children.
+        if not wtree or wtree[0] not in ('', '"'):
+            # The whole token is quoted or empty, nothing to split
+            return [wtree]
+            
+        if wtree[0]=='"':
+            wtree = ['', wtree, '']
+        
+        result = [['', '']]
+        for part in wtree[1:-1]:
+            if isinstance(part, list):
+                if part[0]=='"':
+                    splits = split_positional(part)
+                    if len(splits)<=1:
+                        result[-1] += [part, '']
+                    else:
+                        # Terminate the current split
+                        result[-1] += [splits[0], '']
+                        result += splits[1:-1]
+                        # Create a new split
+                        result += [['', splits[-1], '']]
+                else:
+                    result[-1] += [part, '']
+            else:
+                splits = self._env.split_fields(part)
+                if len(splits)<=1:
+                    # No split
+                    result[-1][-1] += part
+                else:
+                    # Terminate the current resulting part and create a new one
+                    result[-1][-1] += splits[0]
+                    result[-1].append('')
+                    result += [['', r, ''] for r in splits[1:-1]]
+                    result += [['', splits[-1]]]
+        result[-1].append('')
+        
+        # Leading and trailing empty groups come from leading/trailing blanks
+        if result and is_empty(result[-1]):
+            result[-1:] = []
+        if result and is_empty(result[0]):
+            result[:1] = []
+        return result
+        
+    def _expand_pathname(self, wtree):
+        """See [2.6.6 Pathname Expansion]."""
+        if self._env.has_opt('-f'):
+            return [wtree]
+        
+        # All expansions have been performed, only quoted sequences should remain
+        # in the tree. Generate the pattern by folding the tree, escaping special
+        # characters when appear quoted
+        special_chars = '*?[]'
+        
+        def make_pattern(wtree):
+            subpattern = []
+            for part in wtree[1:-1]:
+                if isinstance(part, list):
+                    part = make_pattern(part)
+                elif wtree[0]!='':
+                    for c in part:
+                        # Meta-characters cannot be quoted
+                        if c in special_chars:
+                            raise GlobError()
+                subpattern.append(part)
+            return ''.join(subpattern)
+            
+        def pwd_glob(pattern):
+            cwd = os.getcwd()
+            os.chdir(self._env['PWD'])
+            try:
+                return glob.glob(pattern) 
+            finally:
+                os.chdir(cwd)    
+            
+        #TODO: check working directory issues here wrt relative patterns
+        try:
+            pattern = make_pattern(wtree)
+            paths = pwd_glob(pattern)
+        except GlobError:
+            # BUG: Meta-characters were found in quoted sequences. The should 
+            # have been used literally but this is unsupported in current glob module.
+            # Instead we consider the whole tree must be used literally and
+            # therefore there is no point in globbing. This is wrong when meta
+            # characters are mixed with quoted meta in the same pattern like:
+            # < foo*"py*" >
+            paths = []
+            
+        if not paths:
+            return [wtree]
+        return [['', path, ''] for path in paths]
+        
+    def _remove_quotes(self, wtree):
+        """See [2.6.7 Quote Removal]."""
+        
+        def unquote(wtree):
+            unquoted = []
+            for part in wtree[1:-1]:
+                if isinstance(part, list):
+                    part = unquote(part)
+                unquoted.append(part)
+            return ''.join(unquoted)
+            
+        return ['', unquote(wtree), '']
+        
+    def subshell(self, script=None, ast=None, redirs=None):
+        """Execute the script or AST in a subshell, with inherited redirections
+        if redirs is not None.
+        """
+        if redirs:
+            sub_redirs = redirs
+        else:
+            sub_redirs = redirs.clone()
+        
+        subshell = None    
+        try:
+            subshell = Interpreter(None, self._debugflags, self._env.clone(True),
+                                   sub_redirs, opts=self._options)
+            return subshell.execute_script(script, ast)
+        finally:
+            if not redirs: sub_redirs.close()
+            if subshell: subshell.close()
+        
+    def subshell_output(self, script):
+        """Execute the script in a subshell and return the captured output."""        
+        # Create temporary file to capture subshell output
+        tmpfd, tmppath = tempfile.mkstemp()
+        try:
+            tmpfile = os.fdopen(tmpfd, 'wb')
+            stdout = FileWrapper('w', tmpfile)
+            
+            redirs = Redirections(self._redirs.stdin().dup(),
+                                  stdout,
+                                  self._redirs.stderr().dup())            
+            try:
+                status = self.subshell(script=script, redirs=redirs)
+            finally:
+                redirs.close()
+                redirs = None
+            
+            # Extract subshell standard output
+            tmpfile = open(tmppath, 'rb')
+            try:
+                output = tmpfile.read()
+                return status, output.rstrip('\n')
+            finally:
+                tmpfile.close()
+        finally:
+            os.remove(tmppath)
+
+    def _asynclist(self, cmd):
+        args = (self._env.get_variables(), cmd)
+        arg = encodeargs(args)
+        assert len(args) < 30*1024
+        cmd = ['pysh.bat', '--ast', '-c', arg]
+        p = subprocess.Popen(cmd, cwd=self._env['PWD'])
+        self._children[p.pid] = p
+        self._env['!'] = p.pid
+        return 0
+
+    def wait(self, pids=None):
+        if not pids:
+            pids = self._children.keys()
+
+        status = 127
+        for pid in pids:
+            if pid not in self._children:
+                continue
+            p = self._children.pop(pid)
+            status = p.wait()
+
+        return status
+
diff --git a/bitbake/lib/bb/pysh/lsprof.py b/bitbake/lib/bb/pysh/lsprof.py
new file mode 100644
index 0000000..b1831c2
--- /dev/null
+++ b/bitbake/lib/bb/pysh/lsprof.py
@@ -0,0 +1,116 @@
+#! /usr/bin/env python
+
+import sys
+from _lsprof import Profiler, profiler_entry
+
+__all__ = ['profile', 'Stats']
+
+def profile(f, *args, **kwds):
+    """XXX docstring"""
+    p = Profiler()
+    p.enable(subcalls=True, builtins=True)
+    try:
+        f(*args, **kwds)
+    finally:
+        p.disable()
+    return Stats(p.getstats())
+
+
+class Stats(object):
+    """XXX docstring"""
+
+    def __init__(self, data):
+        self.data = data
+
+    def sort(self, crit="inlinetime"):
+        """XXX docstring"""
+        if crit not in profiler_entry.__dict__:
+            raise ValueError("Can't sort by %s" % crit)
+        self.data.sort(lambda b, a: cmp(getattr(a, crit),
+                                        getattr(b, crit)))
+        for e in self.data:
+            if e.calls:
+                e.calls.sort(lambda b, a: cmp(getattr(a, crit),
+                                              getattr(b, crit)))
+
+    def pprint(self, top=None, file=None, limit=None, climit=None):
+        """XXX docstring"""
+        if file is None:
+            file = sys.stdout
+        d = self.data
+        if top is not None:
+            d = d[:top]
+        cols = "% 12s %12s %11.4f %11.4f   %s\n"
+        hcols = "% 12s %12s %12s %12s %s\n"
+        cols2 = "+%12s %12s %11.4f %11.4f +  %s\n"
+        file.write(hcols % ("CallCount", "Recursive", "Total(ms)",
+                            "Inline(ms)", "module:lineno(function)"))
+        count = 0
+        for e in d:
+            file.write(cols % (e.callcount, e.reccallcount, e.totaltime,
+                               e.inlinetime, label(e.code)))
+            count += 1
+            if limit is not None and count == limit:
+                return
+            ccount = 0
+            if e.calls:
+                for se in e.calls:
+                    file.write(cols % ("+%s" % se.callcount, se.reccallcount,
+                                       se.totaltime, se.inlinetime,
+                                       "+%s" % label(se.code)))
+                    count += 1
+                    ccount += 1
+                    if limit is not None and count == limit:
+                        return
+                    if climit is not None and ccount == climit:
+                        break
+
+    def freeze(self):
+        """Replace all references to code objects with string
+        descriptions; this makes it possible to pickle the instance."""
+
+        # this code is probably rather ickier than it needs to be!
+        for i in range(len(self.data)):
+            e = self.data[i]
+            if not isinstance(e.code, str):
+                self.data[i] = type(e)((label(e.code),) + e[1:])
+            if e.calls:
+                for j in range(len(e.calls)):
+                    se = e.calls[j]
+                    if not isinstance(se.code, str):
+                        e.calls[j] = type(se)((label(se.code),) + se[1:])
+
+_fn2mod = {}
+
+def label(code):
+    if isinstance(code, str):
+        return code
+    try:
+        mname = _fn2mod[code.co_filename]
+    except KeyError:
+        for k, v in sys.modules.items():
+            if v is None:
+                continue
+            if not hasattr(v, '__file__'):
+                continue
+            if not isinstance(v.__file__, str):
+                continue
+            if v.__file__.startswith(code.co_filename):
+                mname = _fn2mod[code.co_filename] = k
+                break
+        else:
+            mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
+
+    return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
+
+
+if __name__ == '__main__':
+    import os
+    sys.argv = sys.argv[1:]
+    if not sys.argv:
+        print >> sys.stderr, "usage: lsprof.py <script> <arguments...>"
+        sys.exit(2)
+    sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
+    stats = profile(execfile, sys.argv[0], globals(), locals())
+    stats.sort()
+    stats.pprint()
diff --git a/bitbake/lib/bb/pysh/pysh.py b/bitbake/lib/bb/pysh/pysh.py
new file mode 100644
index 0000000..b4e6145
--- /dev/null
+++ b/bitbake/lib/bb/pysh/pysh.py
@@ -0,0 +1,167 @@
+# pysh.py - command processing for pysh.
+#
+# Copyright 2007 Patrick Mezard
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+import optparse
+import os
+import sys
+
+import interp
+
+SH_OPT = optparse.OptionParser(prog='pysh', usage="%prog [OPTIONS]", version='0.1')
+SH_OPT.add_option('-c', action='store_true', dest='command_string', default=None, 
+    help='A string that shall be interpreted by the shell as one or more commands')
+SH_OPT.add_option('--redirect-to', dest='redirect_to', default=None, 
+    help='Redirect script commands stdout and stderr to the specified file')
+# See utility_command in builtin.py about the reason for this flag.
+SH_OPT.add_option('--redirected', dest='redirected', action='store_true', default=False, 
+    help='Tell the interpreter that stdout and stderr are actually the same objects, which is really stdout')
+SH_OPT.add_option('--debug-parsing', action='store_true', dest='debug_parsing', default=False, 
+    help='Trace PLY execution')
+SH_OPT.add_option('--debug-tree', action='store_true', dest='debug_tree', default=False, 
+    help='Display the generated syntax tree.')
+SH_OPT.add_option('--debug-cmd', action='store_true', dest='debug_cmd', default=False, 
+    help='Trace command execution before parameters expansion and exit status.')
+SH_OPT.add_option('--debug-utility', action='store_true', dest='debug_utility', default=False, 
+    help='Trace utility calls, after parameters expansions')
+SH_OPT.add_option('--ast', action='store_true', dest='ast', default=False,
+    help='Encoded commands to execute in a subprocess')
+SH_OPT.add_option('--profile', action='store_true', default=False,
+    help='Profile pysh run')
+    
+    
+def split_args(args):
+    # Separate shell arguments from command ones
+    # Just stop at the first argument not starting with a dash. I know, this is completely broken,
+    # it ignores files starting with a dash or may take option values for command file. This is not
+    # supposed to happen for now
+    command_index = len(args)
+    for i,arg in enumerate(args):
+        if not arg.startswith('-'):
+            command_index = i
+            break
+            
+    return args[:command_index], args[command_index:]
+
+
+def fixenv(env):
+    path = env.get('PATH')
+    if path is not None:
+        parts = path.split(os.pathsep)
+        # Remove Windows utilities from PATH, they are useless at best and
+        # some of them (find) may be confused with other utilities.
+        parts = [p for p in parts if 'system32' not in p.lower()]
+        env['PATH'] = os.pathsep.join(parts)
+    if env.get('HOME') is None:
+        # Several utilities, including cvsps, cannot work without
+        # a defined HOME directory.
+        env['HOME'] = os.path.expanduser('~')
+    return env
+
+def _sh(cwd, shargs, cmdargs, options, debugflags=None, env=None):
+    if os.environ.get('PYSH_TEXT') != '1':
+        import msvcrt
+        for fp in (sys.stdin, sys.stdout, sys.stderr):
+            msvcrt.setmode(fp.fileno(), os.O_BINARY)
+
+    hgbin = os.environ.get('PYSH_HGTEXT') != '1'
+    
+    if debugflags is None:
+        debugflags = []
+        if options.debug_parsing:    debugflags.append('debug-parsing')
+        if options.debug_utility:    debugflags.append('debug-utility')
+        if options.debug_cmd:        debugflags.append('debug-cmd')
+        if options.debug_tree:       debugflags.append('debug-tree')
+    
+    if env is None:
+        env = fixenv(dict(os.environ))
+    if cwd is None:
+        cwd = os.getcwd()
+
+    if not cmdargs:
+        # Nothing to do
+        return 0
+
+    ast = None
+    command_file = None
+    if options.command_string:
+        input = cmdargs[0]
+        if not options.ast:
+            input += '\n'
+        else:
+            args, input = interp.decodeargs(input), None
+            env, ast = args
+            cwd = env.get('PWD', cwd)
+    else:
+        command_file = cmdargs[0]
+        arguments = cmdargs[1:]
+
+        prefix = interp.resolve_shebang(command_file, ignoreshell=True)
+        if prefix:
+            input = ' '.join(prefix + [command_file] + arguments)
+        else:
+            # Read commands from file
+            f = file(command_file)
+            try:
+                # Trailing newline to help the parser
+                input = f.read() + '\n'
+            finally:
+                f.close()
+    
+    redirect = None
+    try:
+        if options.redirected:
+            stdout = sys.stdout
+            stderr = stdout
+        elif options.redirect_to:
+            redirect = open(options.redirect_to, 'wb')
+            stdout = redirect
+            stderr = redirect
+        else:
+            stdout = sys.stdout
+            stderr = sys.stderr
+            
+        # TODO: set arguments to environment variables
+        opts = interp.Options()
+        opts.hgbinary = hgbin
+        ip = interp.Interpreter(cwd, debugflags, stdout=stdout, stderr=stderr,
+                                opts=opts)
+        try:
+            # Export given environment in shell object
+            for k,v in env.iteritems():
+                ip.get_env().export(k,v)
+            return ip.execute_script(input, ast, scriptpath=command_file)
+        finally:
+            ip.close()
+    finally:
+        if redirect is not None:
+            redirect.close()
+
+def sh(cwd=None, args=None, debugflags=None, env=None):
+    if args is None:
+        args = sys.argv[1:]
+    shargs, cmdargs = split_args(args)
+    options, shargs = SH_OPT.parse_args(shargs)
+
+    if options.profile:
+        import lsprof
+        p = lsprof.Profiler()
+        p.enable(subcalls=True)
+        try:
+            return _sh(cwd, shargs, cmdargs, options, debugflags, env)
+        finally:
+            p.disable()
+            stats = lsprof.Stats(p.getstats())
+            stats.sort()
+            stats.pprint(top=10, file=sys.stderr, climit=5)
+    else:
+        return _sh(cwd, shargs, cmdargs, options, debugflags, env)
+            
+def main():
+    sys.exit(sh())
+
+if __name__=='__main__':
+    main()
diff --git a/bitbake/lib/bb/pysh/pyshlex.py b/bitbake/lib/bb/pysh/pyshlex.py
new file mode 100644
index 0000000..b301236
--- /dev/null
+++ b/bitbake/lib/bb/pysh/pyshlex.py
@@ -0,0 +1,888 @@
+# pyshlex.py - PLY compatible lexer for pysh.
+#
+# Copyright 2007 Patrick Mezard
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+# TODO:
+# - review all "char in 'abc'" snippets: the empty string can be matched
+# - test line continuations within quoted/expansion strings
+# - eof is buggy wrt sublexers
+# - the lexer cannot really work in pull mode as it would be required to run
+# PLY in pull mode. It was designed to work incrementally and it would not be
+# that hard to enable pull mode.
+import re
+try:
+    s = set()
+    del s
+except NameError:
+    from Set import Set as set
+
+from ply import lex
+from sherrors import *
+
+class NeedMore(Exception):
+    pass
+
+def is_blank(c):
+    return c in (' ', '\t')
+    
+_RE_DIGITS = re.compile(r'^\d+$')
+
+def are_digits(s):
+    return _RE_DIGITS.search(s) is not None
+
+_OPERATORS = dict([
+    ('&&', 'AND_IF'),
+    ('||', 'OR_IF'),
+    (';;', 'DSEMI'),
+    ('<<', 'DLESS'),
+    ('>>', 'DGREAT'),
+    ('<&', 'LESSAND'),
+    ('>&', 'GREATAND'),
+    ('<>', 'LESSGREAT'),
+    ('<<-', 'DLESSDASH'),
+    ('>|', 'CLOBBER'),
+    ('&', 'AMP'),
+    (';', 'COMMA'),
+    ('<', 'LESS'),
+    ('>', 'GREATER'),
+    ('(', 'LPARENS'),
+    (')', 'RPARENS'),
+])
+
+#Make a function to silence pychecker "Local variable shadows global"
+def make_partial_ops():
+    partials = {}
+    for k in _OPERATORS:
+        for i in range(1, len(k)+1):
+            partials[k[:i]] = None
+    return partials  
+
+_PARTIAL_OPERATORS = make_partial_ops()    
+        
+def is_partial_op(s):
+    """Return True if s matches a non-empty subpart of an operator starting
+    at its first character.
+    """
+    return s in _PARTIAL_OPERATORS
+    
+def is_op(s):
+    """If s matches an operator, returns the operator identifier. Return None
+    otherwise.
+    """
+    return _OPERATORS.get(s)
+
+_RESERVEDS = dict([
+    ('if', 'If'),
+    ('then', 'Then'),
+    ('else', 'Else'),
+    ('elif', 'Elif'),
+    ('fi', 'Fi'),
+    ('do', 'Do'),
+    ('done', 'Done'),
+    ('case', 'Case'),
+    ('esac', 'Esac'),
+    ('while', 'While'),
+    ('until', 'Until'),
+    ('for', 'For'),
+    ('{', 'Lbrace'),
+    ('}', 'Rbrace'),
+    ('!', 'Bang'),
+    ('in', 'In'),
+    ('|', 'PIPE'),
+])
+    
+def get_reserved(s):
+    return _RESERVEDS.get(s)
+    
+_RE_NAME = re.compile(r'^[0-9a-zA-Z_]+$')
+
+def is_name(s):
+    return _RE_NAME.search(s) is not None
+
+def find_chars(seq, chars):
+    for i,v in enumerate(seq):
+        if v in chars:
+            return i,v
+    return -1, None
+
+class WordLexer:
+    """WordLexer parse quoted or expansion expressions and return an expression
+    tree. The input string can be any well formed sequence beginning with quoting
+    or expansion character. Embedded expressions are handled recursively. The
+    resulting tree is made of lists and strings. Lists represent quoted or
+    expansion expressions. Each list first element is the opening separator,
+    the last one the closing separator. In-between can be any number of strings
+    or lists for sub-expressions. Non quoted/expansion expression can written as
+    strings or as lists with empty strings as starting and ending delimiters.
+    """
+
+    NAME_CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'
+    NAME_CHARSET = dict(zip(NAME_CHARSET, NAME_CHARSET))
+    
+    SPECIAL_CHARSET = '@*#?-$!0'
+    
+    #Characters which can be escaped depends on the current delimiters
+    ESCAPABLE = {
+        '`': set(['$', '\\', '`']),
+        '"': set(['$', '\\', '`', '"']),
+        "'": set(),
+    }
+        
+    def __init__(self, heredoc = False):
+        # _buffer is the unprocessed input characters buffer
+        self._buffer = []
+        # _stack is empty or contains a quoted list being processed
+        # (this is the DFS path to the quoted expression being evaluated).
+        self._stack = []
+        self._escapable = None
+        # True when parsing unquoted here documents
+        self._heredoc = heredoc
+        
+    def add(self, data, eof=False):
+        """Feed the lexer with more data. If the quoted expression can be
+        delimited, return a tuple (expr, remaining) containing the expression
+        tree and the unconsumed data.
+        Otherwise, raise NeedMore.
+        """
+        self._buffer += list(data)
+        self._parse(eof)
+        
+        result = self._stack[0]
+        remaining = ''.join(self._buffer)
+        self._stack = []
+        self._buffer = []
+        return result, remaining
+        
+    def _is_escapable(self, c, delim=None):
+        if delim is None:
+            if self._heredoc:
+                # Backslashes works as if they were double quoted in unquoted
+                # here-documents
+                delim = '"'
+            else:
+                if len(self._stack)<=1:
+                    return True
+                delim = self._stack[-2][0]
+            
+        escapables = self.ESCAPABLE.get(delim, None)
+        return escapables is None or c in escapables
+        
+    def _parse_squote(self, buf, result, eof):
+        if not buf:
+            raise NeedMore()
+        try:
+            pos = buf.index("'")
+        except ValueError:
+            raise NeedMore()
+        result[-1] += ''.join(buf[:pos])
+        result += ["'"]
+        return pos+1, True
+        
+    def _parse_bquote(self, buf, result, eof):
+        if not buf:
+            raise NeedMore()
+            
+        if buf[0]=='\n':
+            #Remove line continuations
+            result[:] = ['', '', '']
+        elif self._is_escapable(buf[0]):
+            result[-1] += buf[0]
+            result += ['']
+        else:
+            #Keep as such
+            result[:] = ['', '\\'+buf[0], '']
+        
+        return 1, True
+        
+    def _parse_dquote(self, buf, result, eof):
+        if not buf:
+            raise NeedMore()
+        pos, sep = find_chars(buf, '$\\`"')
+        if pos==-1:
+            raise NeedMore()
+            
+        result[-1] += ''.join(buf[:pos])
+        if sep=='"':
+            result += ['"']
+            return pos+1, True
+        else:
+            #Keep everything until the separator and defer processing
+            return pos, False
+            
+    def _parse_command(self, buf, result, eof):
+        if not buf:
+            raise NeedMore()
+
+        chars = '$\\`"\''
+        if result[0] == '$(':
+            chars += ')'
+        pos, sep = find_chars(buf, chars)
+        if pos == -1:
+            raise NeedMore()
+            
+        result[-1] += ''.join(buf[:pos])
+        if (result[0]=='$(' and sep==')') or (result[0]=='`' and sep=='`'):
+            result += [sep]
+            return pos+1, True
+        else:
+            return pos, False
+            
+    def _parse_parameter(self, buf, result, eof):
+        if not buf:
+            raise NeedMore()
+            
+        pos, sep = find_chars(buf, '$\\`"\'}')
+        if pos==-1:
+            raise NeedMore()
+            
+        result[-1] += ''.join(buf[:pos])
+        if sep=='}':
+            result += [sep]
+            return pos+1, True
+        else:
+            return pos, False
+            
+    def _parse_dollar(self, buf, result, eof):
+        sep = result[0]
+        if sep=='$':            
+            if not buf:
+                #TODO: handle empty $
+                raise NeedMore()
+            if buf[0]=='(':
+                if len(buf)==1:
+                    raise NeedMore()
+                    
+                if buf[1]=='(':
+                    result[0] = '$(('
+                    buf[:2] = []
+                else:
+                    result[0] = '$('
+                    buf[:1] = []
+                
+            elif buf[0]=='{':
+                result[0] = '${'
+                buf[:1] = []
+            else:
+                if buf[0] in self.SPECIAL_CHARSET:
+                    result[-1] = buf[0]
+                    read = 1
+                else:
+                    for read,c in enumerate(buf):
+                        if c not in self.NAME_CHARSET:
+                            break
+                    else:
+                        if not eof:
+                            raise NeedMore()
+                        read += 1
+                        
+                    result[-1] += ''.join(buf[0:read])
+                    
+                if not result[-1]:
+                    result[:] = ['', result[0], '']
+                else:
+                    result += [''] 
+                return read,True
+        
+        sep = result[0]    
+        if sep=='$(':
+            parsefunc = self._parse_command
+        elif sep=='${':
+            parsefunc = self._parse_parameter
+        else:
+            raise NotImplementedError(sep)
+            
+        pos, closed = parsefunc(buf, result, eof)
+        return pos, closed
+
+    def _parse(self, eof):
+        buf = self._buffer
+        stack = self._stack
+        recurse = False
+    
+        while 1:
+            if not stack or recurse:
+                if not buf:
+                    raise NeedMore()
+                if buf[0] not in ('"\\`$\''):
+                    raise ShellSyntaxError('Invalid quoted string sequence')
+                stack.append([buf[0], ''])
+                buf[:1] = []
+                recurse = False
+                
+            result = stack[-1]
+            if result[0]=="'":
+                parsefunc = self._parse_squote
+            elif result[0]=='\\':
+                parsefunc = self._parse_bquote
+            elif result[0]=='"':
+                parsefunc = self._parse_dquote
+            elif result[0]=='`':
+                parsefunc = self._parse_command
+            elif result[0][0]=='$':
+                parsefunc = self._parse_dollar
+            else:
+                raise NotImplementedError()
+                
+            read, closed = parsefunc(buf, result, eof)
+                
+            buf[:read] = []
+            if closed:
+                if len(stack)>1:
+                    #Merge in parent expression
+                    parsed = stack.pop()
+                    stack[-1] += [parsed]
+                    stack[-1] += ['']
+                else:
+                    break
+            else:
+                recurse = True
+
+def normalize_wordtree(wtree):
+    """Fold back every literal sequence (delimited with empty strings) into
+    parent sequence.
+    """
+    def normalize(wtree):
+        result = []
+        for part in wtree[1:-1]:
+            if isinstance(part, list):
+                part = normalize(part)
+                if part[0]=='':
+                    #Move the part content back at current level
+                    result += part[1:-1]
+                    continue
+            elif not part:
+                #Remove empty strings
+                continue
+            result.append(part)
+        if not result:
+            result = ['']    
+        return [wtree[0]] + result + [wtree[-1]]
+                
+    return normalize(wtree)
+    
+                
+def make_wordtree(token, here_document=False):
+    """Parse a delimited token and return a tree similar to the ones returned by
+    WordLexer. token may contain any combinations of expansion/quoted fields and
+    non-ones.
+    """    
+    tree = ['']
+    remaining = token
+    delimiters = '\\$`'
+    if not here_document:
+        delimiters += '\'"'
+    
+    while 1:
+        pos, sep = find_chars(remaining, delimiters)
+        if pos==-1:
+            tree += [remaining, '']
+            return normalize_wordtree(tree)
+        tree.append(remaining[:pos])
+        remaining = remaining[pos:]
+        
+        try:
+            result, remaining = WordLexer(heredoc = here_document).add(remaining, True)
+        except NeedMore:
+            raise ShellSyntaxError('Invalid token "%s"')
+        tree.append(result)
+        
+                
+def wordtree_as_string(wtree):
+    """Rewrite an expression tree generated by make_wordtree as string."""
+    def visit(node, output):
+        for child in node:
+            if isinstance(child, list):
+                visit(child, output)
+            else:
+                output.append(child)
+    
+    output = []
+    visit(wtree, output)
+    return ''.join(output)
+    
+    
+def unquote_wordtree(wtree):
+    """Fold the word tree while removing quotes everywhere. Other expansion
+    sequences are joined as such.
+    """
+    def unquote(wtree):
+        unquoted = []
+        if wtree[0] in ('', "'", '"', '\\'):
+            wtree = wtree[1:-1]
+            
+        for part in wtree:
+            if isinstance(part, list):
+                part = unquote(part)
+            unquoted.append(part)
+        return ''.join(unquoted)
+            
+    return unquote(wtree)
+    
+    
+class HereDocLexer:
+    """HereDocLexer delimits whatever comes from the here-document starting newline
+    not included to the closing delimiter line included.
+    """
+    def __init__(self, op, delim):
+        assert op in ('<<', '<<-')
+        if not delim:
+            raise ShellSyntaxError('invalid here document delimiter %s' % str(delim))
+            
+        self._op = op
+        self._delim = delim
+        self._buffer = []
+        self._token = []
+        
+    def add(self, data, eof):
+        """If the here-document was delimited, return a tuple (content, remaining).
+        Raise NeedMore() otherwise.
+        """
+        self._buffer += list(data)
+        self._parse(eof)
+        token = ''.join(self._token)
+        remaining = ''.join(self._buffer)
+        self._token, self._remaining = [], []
+        return token, remaining
+    
+    def _parse(self, eof):
+        while 1:
+            #Look for first unescaped newline. Quotes may be ignored
+            escaped = False
+            for i,c in enumerate(self._buffer):
+                if escaped:
+                    escaped = False
+                elif c=='\\':
+                    escaped = True
+                elif c=='\n':
+                    break
+            else:
+                i = -1
+                    
+            if i==-1 or self._buffer[i]!='\n':
+                if not eof:
+                    raise NeedMore()
+                #No more data, maybe the last line is closing delimiter
+                line = ''.join(self._buffer)
+                eol = ''
+                self._buffer[:] = []
+            else:
+                line = ''.join(self._buffer[:i])
+                eol = self._buffer[i]
+                self._buffer[:i+1] = []
+            
+            if self._op=='<<-':
+                line = line.lstrip('\t')
+                
+            if line==self._delim:
+                break
+                
+            self._token += [line, eol]
+            if i==-1:
+                break
+    
+class Token:
+    #TODO: check this is still in use
+    OPERATOR = 'OPERATOR'
+    WORD = 'WORD'
+    
+    def __init__(self):
+        self.value = ''
+        self.type = None
+        
+    def __getitem__(self, key):
+        #Behave like a two elements tuple
+        if key==0:
+            return self.type
+        if key==1:
+            return self.value
+        raise IndexError(key)
+               
+               
+class HereDoc:
+    def __init__(self, op, name=None):
+        self.op = op
+        self.name = name
+        self.pendings = []
+               
+TK_COMMA        = 'COMMA'
+TK_AMPERSAND    = 'AMP'
+TK_OP           = 'OP'
+TK_TOKEN        = 'TOKEN'
+TK_COMMENT      = 'COMMENT'
+TK_NEWLINE      = 'NEWLINE' 
+TK_IONUMBER     = 'IO_NUMBER'
+TK_ASSIGNMENT   = 'ASSIGNMENT_WORD'
+TK_HERENAME     = 'HERENAME'
+
+class Lexer:
+    """Main lexer.
+    
+    Call add() until the script AST is returned.
+    """
+    # Here-document handling makes the whole thing more complex because they basically
+    # force tokens to be reordered: here-content must come right after the operator
+    # and the here-document name, while some other tokens might be following the
+    # here-document expression on the same line.
+    #
+    # So, here-doc states are basically:
+    #   *self._state==ST_NORMAL
+    #       - self._heredoc.op is None: no here-document
+    #       - self._heredoc.op is not None but name is: here-document operator matched,
+    #           waiting for the document name/delimiter
+    #       - self._heredoc.op and name are not None: here-document is ready, following
+    #           tokens are being stored and will be pushed again when the document is
+    #           completely parsed.
+    #   *self._state==ST_HEREDOC
+    #       - The here-document is being delimited by self._herelexer. Once it is done
+    #           the content is pushed in front of the pending token list then all these
+    #           tokens are pushed once again.
+    ST_NORMAL       = 'ST_NORMAL'
+    ST_OP           = 'ST_OP'
+    ST_BACKSLASH    = 'ST_BACKSLASH'
+    ST_QUOTED       = 'ST_QUOTED'
+    ST_COMMENT      = 'ST_COMMENT'
+    ST_HEREDOC      = 'ST_HEREDOC'
+    
+    #Match end of backquote strings
+    RE_BACKQUOTE_END = re.compile(r'(?<!\\)(`)')
+
+    def __init__(self, parent_state = None):
+        self._input = []
+        self._pos = 0
+        
+        self._token = ''
+        self._type = TK_TOKEN
+        
+        self._state = self.ST_NORMAL
+        self._parent_state = parent_state
+        self._wordlexer = None
+        
+        self._heredoc = HereDoc(None)
+        self._herelexer = None
+        
+        ### Following attributes are not used for delimiting token and can safely
+        ### be changed after here-document detection (see _push_toke)
+        
+        # Count the number of tokens following a 'For' reserved word. Needed to
+        # return an 'In' reserved word if it comes in third place.
+        self._for_count = None
+        
+    def add(self, data, eof=False):
+        """Feed the lexer with data.
+        
+        When eof is set to True, returns unconsumed data or raise if the lexer
+        is in the middle of a delimiting operation.
+        Raise NeedMore otherwise.
+        """
+        self._input += list(data)
+        self._parse(eof)
+        self._input[:self._pos] = []
+        return ''.join(self._input)
+        
+    def _parse(self, eof):            
+        while self._state:
+            if self._pos>=len(self._input):
+                if not eof:
+                    raise NeedMore()
+                elif self._state not in (self.ST_OP, self.ST_QUOTED, self.ST_HEREDOC):
+                    #Delimit the current token and leave cleanly
+                    self._push_token('')
+                    break
+                else:
+                    #Let the sublexer handle the eof themselves
+                    pass
+                
+            if self._state==self.ST_NORMAL:
+                self._parse_normal()
+            elif self._state==self.ST_COMMENT:
+                self._parse_comment()
+            elif self._state==self.ST_OP:
+                self._parse_op(eof)
+            elif self._state==self.ST_QUOTED:
+                self._parse_quoted(eof)
+            elif self._state==self.ST_HEREDOC:
+                self._parse_heredoc(eof)
+            else:
+                assert False, "Unknown state " + str(self._state)
+                
+        if self._heredoc.op is not None:
+            raise ShellSyntaxError('missing here-document delimiter')
+                
+    def _parse_normal(self):
+        c = self._input[self._pos]
+        if c=='\n':
+            self._push_token(c)
+            self._token = c
+            self._type = TK_NEWLINE
+            self._push_token('')
+            self._pos += 1
+        elif c in ('\\', '\'', '"', '`', '$'):
+            self._state = self.ST_QUOTED
+        elif is_partial_op(c):
+            self._push_token(c)
+            
+            self._type = TK_OP
+            self._token += c
+            self._pos += 1
+            self._state = self.ST_OP
+        elif is_blank(c):
+            self._push_token(c)
+            
+            #Discard blanks
+            self._pos += 1
+        elif self._token:
+            self._token += c
+            self._pos += 1
+        elif c=='#':
+            self._state = self.ST_COMMENT
+            self._type = TK_COMMENT
+            self._pos += 1
+        else:
+            self._pos += 1
+            self._token += c          
+                
+    def _parse_op(self, eof):
+        assert self._token
+        
+        while 1:
+            if self._pos>=len(self._input):
+                if not eof:
+                    raise NeedMore()
+                c = ''
+            else:                
+                c = self._input[self._pos]
+                
+            op = self._token + c
+            if c and is_partial_op(op):
+                #Still parsing an operator
+                self._token = op
+                self._pos += 1
+            else:            
+                #End of operator
+                self._push_token(c)                    
+                self._state = self.ST_NORMAL
+                break
+                
+    def _parse_comment(self):
+        while 1:
+            if self._pos>=len(self._input):
+                raise NeedMore()
+                
+            c = self._input[self._pos]
+            if c=='\n':
+                #End of comment, do not consume the end of line
+                self._state = self.ST_NORMAL
+                break
+            else:
+                self._token += c
+                self._pos += 1
+                
+    def _parse_quoted(self, eof):
+        """Precondition: the starting backquote/dollar is still in the input queue."""
+        if not self._wordlexer:
+            self._wordlexer = WordLexer()
+        
+        if self._pos<len(self._input):
+             #Transfer input queue character into the subparser
+            input = self._input[self._pos:]
+            self._pos += len(input)
+            
+        wtree, remaining = self._wordlexer.add(input, eof)
+        self._wordlexer = None
+        self._token += wordtree_as_string(wtree)
+        
+        #Put unparsed character back in the input queue
+        if remaining:
+            self._input[self._pos:self._pos] = list(remaining)          
+        self._state = self.ST_NORMAL
+        
+    def _parse_heredoc(self, eof):
+        assert not self._token
+        
+        if self._herelexer is None:
+            self._herelexer = HereDocLexer(self._heredoc.op, self._heredoc.name)
+        
+        if self._pos<len(self._input):
+             #Transfer input queue character into the subparser
+            input = self._input[self._pos:]
+            self._pos += len(input)
+        
+        self._token, remaining = self._herelexer.add(input, eof)
+        
+        #Reset here-document state
+        self._herelexer = None
+        heredoc, self._heredoc = self._heredoc, HereDoc(None)
+        if remaining:
+            self._input[self._pos:self._pos] = list(remaining)
+        self._state = self.ST_NORMAL
+        
+        #Push pending tokens
+        heredoc.pendings[:0] = [(self._token, self._type, heredoc.name)]
+        for token, type, delim in heredoc.pendings:
+            self._token = token
+            self._type = type
+            self._push_token(delim)
+                     
+    def _push_token(self, delim):
+        if not self._token:
+            return 0
+            
+        if self._heredoc.op is not None:
+            if self._heredoc.name is None:
+                #Here-document name
+                if self._type!=TK_TOKEN:
+                    raise ShellSyntaxError("expecting here-document name, got '%s'" % self._token)
+                self._heredoc.name = unquote_wordtree(make_wordtree(self._token))
+                self._type = TK_HERENAME
+            else:
+                #Capture all tokens until the newline starting the here-document
+                if self._type==TK_NEWLINE:
+                    assert self._state==self.ST_NORMAL
+                    self._state = self.ST_HEREDOC    
+                
+                self._heredoc.pendings.append((self._token, self._type, delim))    
+                self._token = ''
+                self._type = TK_TOKEN
+                return 1
+                
+        # BEWARE: do not change parser state from here to the end of the function:
+        # when parsing between an here-document operator to the end of the line
+        # tokens are stored in self._heredoc.pendings. Therefore, they will not
+        # reach the section below.
+                    
+        #Check operators
+        if self._type==TK_OP:
+            #False positive because of partial op matching
+            op = is_op(self._token)
+            if not op:
+                self._type = TK_TOKEN
+            else:
+                #Map to the specific operator
+                self._type = op
+                if self._token in ('<<', '<<-'):
+                    #Done here rather than in _parse_op because there is no need
+                    #to change the parser state since we are still waiting for
+                    #the here-document name
+                    if self._heredoc.op is not None:
+                        raise ShellSyntaxError("syntax error near token '%s'" % self._token)
+                    assert self._heredoc.op is None
+                    self._heredoc.op = self._token
+                
+        if self._type==TK_TOKEN:            
+            if '=' in self._token and not delim:
+                if self._token.startswith('='):
+                    #Token is a WORD... a TOKEN that is.
+                    pass
+                else:
+                    prev = self._token[:self._token.find('=')]
+                    if is_name(prev):
+                        self._type = TK_ASSIGNMENT
+                    else:
+                        #Just a token (unspecified)
+                        pass
+            else:
+                reserved = get_reserved(self._token)
+                if reserved is not None:
+                    if reserved=='In' and self._for_count!=2:
+                        #Sorry, not a reserved word after all
+                        pass
+                    else:
+                        self._type = reserved
+                        if reserved in ('For', 'Case'):
+                            self._for_count = 0                    
+                elif are_digits(self._token) and delim in ('<', '>'):
+                    #Detect IO_NUMBER
+                    self._type = TK_IONUMBER
+                elif self._token==';':
+                    self._type = TK_COMMA
+                elif self._token=='&':
+                    self._type = TK_AMPERSAND
+        elif self._type==TK_COMMENT:
+            #Comments are not part of sh grammar, ignore them
+            self._token = ''
+            self._type = TK_TOKEN
+            return 0
+        
+        if self._for_count is not None:
+            #Track token count in 'For' expression to detect 'In' reserved words.
+            #Can only be in third position, no need to go beyond
+            self._for_count += 1
+            if self._for_count==3:
+                self._for_count = None
+                
+        self.on_token((self._token, self._type))
+        self._token = ''
+        self._type = TK_TOKEN
+        return 1
+                        
+    def on_token(self, token):
+        raise NotImplementedError
+                 
+
+tokens = [
+    TK_TOKEN,
+# To silence yacc unused token warnings
+#    TK_COMMENT,
+    TK_NEWLINE,
+    TK_IONUMBER,
+    TK_ASSIGNMENT,
+    TK_HERENAME,
+]            
+
+#Add specific operators
+tokens += _OPERATORS.values()
+#Add reserved words
+tokens += _RESERVEDS.values()
+            
+class PLYLexer(Lexer):
+    """Bridge Lexer and PLY lexer interface."""
+    def __init__(self):
+        Lexer.__init__(self)
+        self._tokens = []
+        self._current = 0
+        self.lineno = 0
+
+    def on_token(self, token):
+        value, type = token
+
+        self.lineno = 0
+        t = lex.LexToken()
+        t.value = value
+        t.type = type
+        t.lexer = self
+        t.lexpos = 0
+        t.lineno = 0
+        
+        self._tokens.append(t)
+        
+    def is_empty(self):
+        return not bool(self._tokens)
+        
+    #PLY compliant interface
+    def token(self):
+        if self._current>=len(self._tokens):
+            return None
+        t = self._tokens[self._current]
+        self._current += 1
+        return t      
+        
+        
+def get_tokens(s):
+    """Parse the input string and return a tuple (tokens, unprocessed) where
+    tokens is a list of parsed tokens and unprocessed is the part of the input
+    string left untouched by the lexer.
+    """
+    lexer = PLYLexer()
+    untouched = lexer.add(s, True) 
+    tokens = []
+    while 1:
+        token = lexer.token()
+        if token is None:
+            break
+        tokens.append(token)
+        
+    tokens = [(t.value, t.type) for t in tokens]
+    return tokens, untouched        
diff --git a/bitbake/lib/bb/pysh/pyshyacc.py b/bitbake/lib/bb/pysh/pyshyacc.py
new file mode 100644
index 0000000..e8e80aa
--- /dev/null
+++ b/bitbake/lib/bb/pysh/pyshyacc.py
@@ -0,0 +1,779 @@
+# pyshyacc.py - PLY grammar definition for pysh
+#
+# Copyright 2007 Patrick Mezard
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+"""PLY grammar file.
+"""
+import os.path
+import sys
+
+import pyshlex
+tokens = pyshlex.tokens
+
+from ply import yacc
+import sherrors
+    
+class IORedirect:
+    def __init__(self, op, filename, io_number=None):
+        self.op = op
+        self.filename = filename
+        self.io_number = io_number
+        
+class HereDocument:
+    def __init__(self, op, name, content, io_number=None):
+        self.op = op
+        self.name = name
+        self.content = content
+        self.io_number = io_number
+
+def make_io_redirect(p):
+    """Make an IORedirect instance from the input 'io_redirect' production."""
+    name, io_number, io_target = p
+    assert name=='io_redirect'
+    
+    if io_target[0]=='io_file':
+        io_type, io_op, io_file = io_target
+        return IORedirect(io_op, io_file, io_number)
+    elif io_target[0]=='io_here':
+        io_type, io_op, io_name, io_content = io_target
+        return HereDocument(io_op, io_name, io_content, io_number)
+    else:
+        assert False, "Invalid IO redirection token %s" % repr(io_type)
+        
+class SimpleCommand:
+    """
+    assigns contains (name, value) pairs.
+    """
+    def __init__(self, words, redirs, assigns):
+        self.words = list(words)
+        self.redirs = list(redirs)
+        self.assigns = list(assigns)
+
+class Pipeline:
+    def __init__(self, commands, reverse_status=False):
+        self.commands = list(commands)
+        assert self.commands    #Grammar forbids this
+        self.reverse_status = reverse_status
+        
+class AndOr:
+    def __init__(self, op, left, right):
+        self.op = str(op)
+        self.left = left
+        self.right = right
+        
+class ForLoop:
+    def __init__(self, name, items, cmds):
+        self.name = str(name)
+        self.items = list(items)
+        self.cmds = list(cmds)
+        
+class WhileLoop:
+    def __init__(self, condition, cmds):
+        self.condition = list(condition)
+        self.cmds = list(cmds)
+        
+class UntilLoop:
+    def __init__(self, condition, cmds):
+        self.condition = list(condition)
+        self.cmds = list(cmds)
+
+class FunDef:
+    def __init__(self, name, body):
+        self.name = str(name)
+        self.body = body
+        
+class BraceGroup:
+    def __init__(self, cmds):
+        self.cmds = list(cmds)
+        
+class IfCond:
+    def __init__(self, cond, if_cmds, else_cmds):
+        self.cond = list(cond)
+        self.if_cmds = if_cmds
+        self.else_cmds = else_cmds
+
+class Case:
+    def __init__(self, name, items):
+        self.name = name
+        self.items = items
+        
+class SubShell:
+    def __init__(self, cmds):
+        self.cmds = cmds
+
+class RedirectList:
+    def __init__(self, cmd, redirs):
+        self.cmd = cmd
+        self.redirs = list(redirs)
+        
+def get_production(productions, ptype):
+    """productions must be a list of production tuples like (name, obj) where
+    name is the production string identifier.
+    Return the first production named 'ptype'. Raise KeyError if None can be
+    found.
+    """
+    for production in productions:
+        if production is not None and production[0]==ptype:
+            return production
+    raise KeyError(ptype)
+    
+#-------------------------------------------------------------------------------
+# PLY grammar definition
+#-------------------------------------------------------------------------------
+
+def p_multiple_commands(p):
+    """multiple_commands : newline_sequence
+                         | complete_command
+                         | multiple_commands complete_command"""
+    if len(p)==2:
+        if p[1] is not None:
+            p[0] = [p[1]]
+        else:
+            p[0] = []
+    else:
+        p[0] = p[1] + [p[2]]
+
+def p_complete_command(p):
+    """complete_command : list separator
+                        | list"""
+    if len(p)==3 and p[2] and p[2][1] == '&':
+        p[0] = ('async', p[1])
+    else:
+        p[0] = p[1]
+                 
+def p_list(p):
+    """list : list separator_op and_or
+            |                   and_or"""
+    if len(p)==2:
+        p[0] = [p[1]]
+    else:
+        #if p[2]!=';':
+        #    raise NotImplementedError('AND-OR list asynchronous execution is not implemented')
+        p[0] = p[1] + [p[3]]
+       
+def p_and_or(p):
+    """and_or : pipeline
+              | and_or AND_IF linebreak pipeline
+              | and_or OR_IF  linebreak pipeline"""
+    if len(p)==2:
+        p[0] = p[1]
+    else:
+        p[0] = ('and_or', AndOr(p[2], p[1], p[4]))
+        
+def p_maybe_bang_word(p):
+    """maybe_bang_word : Bang"""
+    p[0] = ('maybe_bang_word', p[1])
+            
+def p_pipeline(p):
+    """pipeline : pipe_sequence
+                | bang_word pipe_sequence"""
+    if len(p)==3:
+        p[0] = ('pipeline', Pipeline(p[2][1:], True))
+    else:
+        p[0] = ('pipeline', Pipeline(p[1][1:]))
+
+def p_pipe_sequence(p):
+    """pipe_sequence : command
+                     | pipe_sequence PIPE linebreak command"""
+    if len(p)==2:
+        p[0] = ['pipe_sequence', p[1]]
+    else:
+        p[0] = p[1] + [p[4]]
+
+def p_command(p):
+    """command : simple_command
+               | compound_command
+               | compound_command redirect_list
+               | function_definition"""
+        
+    if p[1][0] in ( 'simple_command', 
+                    'for_clause',
+                    'while_clause',
+                    'until_clause',
+                    'case_clause',
+                    'if_clause',
+                    'function_definition',
+                    'subshell',
+                    'brace_group',):
+        if len(p) == 2:
+            p[0] = p[1]
+        else:
+            p[0] = ('redirect_list', RedirectList(p[1], p[2][1:]))
+    else:
+        raise NotImplementedError('%s command is not implemented' % repr(p[1][0]))
+
+def p_compound_command(p):
+    """compound_command : brace_group
+                        | subshell
+                        | for_clause
+                        | case_clause
+                        | if_clause
+                        | while_clause
+                        | until_clause"""
+    p[0] = p[1]
+
+def p_subshell(p):
+    """subshell : LPARENS compound_list RPARENS"""
+    p[0] = ('subshell', SubShell(p[2][1:]))
+
+def p_compound_list(p):
+    """compound_list : term
+                     | newline_list term
+                     |              term separator
+                     | newline_list term separator"""
+    productions = p[1:]           
+    try:
+        sep = get_production(productions, 'separator')
+        if sep[1]!=';':
+            raise NotImplementedError()
+    except KeyError:
+        pass
+    term = get_production(productions, 'term')
+    p[0] = ['compound_list'] + term[1:]
+
+def p_term(p):
+    """term : term separator and_or
+            |                and_or"""
+    if len(p)==2:
+        p[0] = ['term', p[1]]
+    else:
+        if p[2] is not None and p[2][1] == '&':
+            p[0] = ['term', ('async', p[1][1:])] + [p[3]]
+        else:
+            p[0] = p[1] + [p[3]]
+            
+def p_maybe_for_word(p):
+    # Rearrange 'For' priority wrt TOKEN. See p_for_word
+    """maybe_for_word : For"""
+    p[0] = ('maybe_for_word', p[1])
+
+def p_for_clause(p):
+    """for_clause : for_word name linebreak                            do_group
+                  | for_word name linebreak in          sequential_sep do_group
+                  | for_word name linebreak in wordlist sequential_sep do_group"""
+    productions = p[1:]
+    do_group = get_production(productions, 'do_group')
+    try:
+        items = get_production(productions, 'in')[1:]
+    except KeyError:
+        raise NotImplementedError('"in" omission is not implemented')
+        
+    try:
+        items = get_production(productions, 'wordlist')[1:]
+    except KeyError:
+        items = []
+        
+    name = p[2]
+    p[0] = ('for_clause', ForLoop(name, items, do_group[1:]))
+
+def p_name(p):
+    """name : token""" #Was NAME instead of token
+    p[0] = p[1]
+
+def p_in(p):
+    """in : In"""
+    p[0] = ('in', p[1])
+
+def p_wordlist(p):
+    """wordlist : wordlist token
+                |          token"""
+    if len(p)==2:
+        p[0] = ['wordlist', ('TOKEN', p[1])]
+    else:
+        p[0] = p[1] + [('TOKEN', p[2])]
+
+def p_case_clause(p):
+    """case_clause : Case token linebreak in linebreak case_list    Esac
+                   | Case token linebreak in linebreak case_list_ns Esac
+                   | Case token linebreak in linebreak              Esac"""
+    if len(p) < 8:
+        items = []
+    else:
+        items = p[6][1:]
+    name = p[2]
+    p[0] = ('case_clause', Case(name, [c[1] for c in items]))
+       
+def p_case_list_ns(p):
+    """case_list_ns : case_list case_item_ns
+                    |           case_item_ns"""
+    p_case_list(p)
+      
+def p_case_list(p):
+    """case_list : case_list case_item
+                 |           case_item"""
+    if len(p)==2:
+        p[0] = ['case_list', p[1]]
+    else:
+        p[0] = p[1] + [p[2]]
+        
+def p_case_item_ns(p):
+    """case_item_ns :         pattern RPARENS               linebreak
+                    |         pattern RPARENS compound_list linebreak
+                    | LPARENS pattern RPARENS               linebreak
+                    | LPARENS pattern RPARENS compound_list linebreak"""
+    p_case_item(p)
+                 
+def p_case_item(p):
+    """case_item :         pattern RPARENS linebreak     DSEMI linebreak
+                 |         pattern RPARENS compound_list DSEMI linebreak
+                 | LPARENS pattern RPARENS linebreak     DSEMI linebreak
+                 | LPARENS pattern RPARENS compound_list DSEMI linebreak"""
+    if len(p) < 7:
+        name = p[1][1:]
+    else:
+        name = p[2][1:]
+
+    try:
+        cmds = get_production(p[1:], "compound_list")[1:]
+    except KeyError:
+        cmds = []
+
+    p[0] = ('case_item', (name, cmds))
+                 
+def p_pattern(p):
+    """pattern :              token
+               | pattern PIPE token"""
+    if len(p)==2:
+        p[0] = ['pattern', ('TOKEN', p[1])]
+    else:
+        p[0] = p[1] + [('TOKEN', p[2])]
+
+def p_maybe_if_word(p):
+    # Rearrange 'If' priority wrt TOKEN. See p_if_word
+    """maybe_if_word : If"""
+    p[0] = ('maybe_if_word', p[1])
+
+def p_maybe_then_word(p):
+    # Rearrange 'Then' priority wrt TOKEN. See p_then_word
+    """maybe_then_word : Then"""
+    p[0] = ('maybe_then_word', p[1])
+                 
+def p_if_clause(p):
+    """if_clause : if_word compound_list then_word compound_list else_part Fi
+                 | if_word compound_list then_word compound_list           Fi"""
+    else_part = []
+    if len(p)==7:
+        else_part = p[5]
+    p[0] = ('if_clause', IfCond(p[2][1:], p[4][1:], else_part))
+                 
+def p_else_part(p):
+    """else_part : Elif compound_list then_word compound_list else_part
+                 | Elif compound_list then_word compound_list
+                 | Else compound_list"""
+    if len(p)==3:
+        p[0] = p[2][1:]
+    else:
+        else_part = []
+        if len(p)==6:
+            else_part = p[5]
+        p[0] = ('elif', IfCond(p[2][1:], p[4][1:], else_part))
+                 
+def p_while_clause(p):
+    """while_clause : While compound_list do_group"""
+    p[0] = ('while_clause', WhileLoop(p[2][1:], p[3][1:]))
+    
+def p_maybe_until_word(p):
+    # Rearrange 'Until' priority wrt TOKEN. See p_until_word
+    """maybe_until_word : Until"""
+    p[0] = ('maybe_until_word', p[1])
+           
+def p_until_clause(p):
+    """until_clause : until_word compound_list do_group"""
+    p[0] = ('until_clause', UntilLoop(p[2][1:], p[3][1:]))
+                 
+def p_function_definition(p):
+    """function_definition : fname LPARENS RPARENS linebreak function_body"""
+    p[0] = ('function_definition', FunDef(p[1], p[5]))
+                 
+def p_function_body(p):
+    """function_body : compound_command
+                     | compound_command redirect_list"""
+    if len(p)!=2:
+        raise NotImplementedError('functions redirections lists are not implemented')    
+    p[0] = p[1]    
+
+def p_fname(p):
+    """fname : TOKEN""" #Was NAME instead of token
+    p[0] = p[1]
+
+def p_brace_group(p):
+    """brace_group : Lbrace compound_list Rbrace"""
+    p[0] = ('brace_group', BraceGroup(p[2][1:]))
+
+def p_maybe_done_word(p):
+    #See p_assignment_word for details.
+    """maybe_done_word : Done"""
+    p[0] = ('maybe_done_word', p[1])
+
+def p_maybe_do_word(p):
+    """maybe_do_word : Do"""
+    p[0] = ('maybe_do_word', p[1])
+
+def p_do_group(p):
+    """do_group : do_word compound_list done_word"""
+    #Do group contains a list of AndOr
+    p[0] = ['do_group'] + p[2][1:]
+
+def p_simple_command(p):
+    """simple_command : cmd_prefix cmd_word cmd_suffix
+                      | cmd_prefix cmd_word
+                      | cmd_prefix
+                      | cmd_name cmd_suffix
+                      | cmd_name"""
+    words, redirs, assigns = [], [], []
+    for e in p[1:]:
+        name = e[0]
+        if name in ('cmd_prefix', 'cmd_suffix'):
+            for sube in e[1:]:
+                subname = sube[0]
+                if subname=='io_redirect':
+                    redirs.append(make_io_redirect(sube))
+                elif subname=='ASSIGNMENT_WORD':
+                    assigns.append(sube)
+                else:
+                    words.append(sube)
+        elif name in ('cmd_word', 'cmd_name'):
+            words.append(e)
+            
+    cmd = SimpleCommand(words, redirs, assigns)
+    p[0] = ('simple_command', cmd)
+
+def p_cmd_name(p):
+    """cmd_name : TOKEN"""
+    p[0] = ('cmd_name', p[1])
+    
+def p_cmd_word(p):
+    """cmd_word : token"""
+    p[0] = ('cmd_word', p[1])
+
+def p_maybe_assignment_word(p):
+    #See p_assignment_word for details.
+    """maybe_assignment_word : ASSIGNMENT_WORD"""
+    p[0] = ('maybe_assignment_word', p[1])
+    
+def p_cmd_prefix(p):
+    """cmd_prefix :            io_redirect
+                  | cmd_prefix io_redirect
+                  |            assignment_word
+                  | cmd_prefix assignment_word"""
+    try:
+        prefix = get_production(p[1:], 'cmd_prefix')
+    except KeyError:
+        prefix = ['cmd_prefix']
+        
+    try:
+        value = get_production(p[1:], 'assignment_word')[1]
+        value = ('ASSIGNMENT_WORD', value.split('=', 1))
+    except KeyError:        
+        value = get_production(p[1:], 'io_redirect')
+    p[0] = prefix + [value]
+                                  
+def p_cmd_suffix(p):
+    """cmd_suffix   :            io_redirect
+                    | cmd_suffix io_redirect
+                    |            token
+                    | cmd_suffix token
+                    |            maybe_for_word
+                    | cmd_suffix maybe_for_word
+                    |            maybe_done_word
+                    | cmd_suffix maybe_done_word
+                    |            maybe_do_word
+                    | cmd_suffix maybe_do_word
+                    |            maybe_until_word
+                    | cmd_suffix maybe_until_word
+                    |            maybe_assignment_word
+                    | cmd_suffix maybe_assignment_word
+                    |            maybe_if_word
+                    | cmd_suffix maybe_if_word
+                    |            maybe_then_word
+                    | cmd_suffix maybe_then_word
+                    |            maybe_bang_word
+                    | cmd_suffix maybe_bang_word"""
+    try:
+        suffix = get_production(p[1:], 'cmd_suffix')
+        token = p[2]
+    except KeyError:
+        suffix = ['cmd_suffix']
+        token = p[1]
+        
+    if isinstance(token, tuple):
+        if token[0]=='io_redirect':
+            p[0] = suffix + [token]
+        else:
+            #Convert maybe_*  to TOKEN if necessary
+            p[0] = suffix + [('TOKEN', token[1])]
+    else:
+        p[0] = suffix + [('TOKEN', token)]
+                 
+def p_redirect_list(p):
+    """redirect_list : io_redirect
+                     | redirect_list io_redirect"""
+    if len(p) == 2:
+        p[0] = ['redirect_list', make_io_redirect(p[1])]
+    else:
+        p[0] = p[1] + [make_io_redirect(p[2])]
+    
+def p_io_redirect(p):
+    """io_redirect :           io_file
+                   | IO_NUMBER io_file
+                   |           io_here
+                   | IO_NUMBER io_here"""
+    if len(p)==3:
+        p[0] = ('io_redirect', p[1], p[2])
+    else:
+        p[0] = ('io_redirect', None, p[1])
+    
+def p_io_file(p):
+    #Return the tuple (operator, filename)
+    """io_file : LESS      filename
+               | LESSAND   filename
+               | GREATER   filename
+               | GREATAND  filename
+               | DGREAT    filename
+               | LESSGREAT filename
+               | CLOBBER   filename"""
+    #Extract the filename from the file
+    p[0] = ('io_file', p[1], p[2][1])
+
+def p_filename(p):
+    #Return the filename
+    """filename : TOKEN"""
+    p[0] = ('filename', p[1])
+        
+def p_io_here(p):
+    """io_here : DLESS here_end
+               | DLESSDASH here_end"""
+    p[0] = ('io_here', p[1], p[2][1], p[2][2])
+
+def p_here_end(p):
+    """here_end : HERENAME TOKEN"""
+    p[0] = ('here_document', p[1], p[2])
+    
+def p_newline_sequence(p):
+    # Nothing in the grammar can handle leading NEWLINE productions, so add
+    # this one with the lowest possible priority relatively to newline_list.
+    """newline_sequence : newline_list"""
+    p[0] = None
+    
+def p_newline_list(p):
+    """newline_list : NEWLINE
+                    | newline_list NEWLINE"""
+    p[0] = None
+                    
+def p_linebreak(p):
+    """linebreak : newline_list
+                 | empty"""
+    p[0] = None
+
+def p_separator_op(p):                 
+    """separator_op : COMMA
+                    | AMP"""
+    p[0] = p[1]
+
+def p_separator(p):
+    """separator : separator_op linebreak
+                 | newline_list"""
+    if len(p)==2:
+        #Ignore newlines
+        p[0] = None
+    else:
+        #Keep the separator operator
+        p[0] = ('separator', p[1])
+                 
+def p_sequential_sep(p):
+    """sequential_sep : COMMA linebreak
+                      | newline_list"""
+    p[0] = None
+
+# Low priority TOKEN => for_word conversion.
+# Let maybe_for_word be used as a token when necessary in higher priority
+# rules. 
+def p_for_word(p):
+    """for_word : maybe_for_word"""
+    p[0] = p[1]
+
+def p_if_word(p):
+    """if_word : maybe_if_word"""
+    p[0] = p[1]
+
+def p_then_word(p):
+    """then_word : maybe_then_word"""
+    p[0] = p[1]
+
+def p_done_word(p):
+    """done_word : maybe_done_word"""
+    p[0] = p[1]
+
+def p_do_word(p):
+    """do_word : maybe_do_word"""
+    p[0] = p[1]
+    
+def p_until_word(p):
+    """until_word : maybe_until_word"""
+    p[0] = p[1]
+    
+def p_assignment_word(p):
+    """assignment_word : maybe_assignment_word"""
+    p[0] = ('assignment_word', p[1][1])
+    
+def p_bang_word(p):
+    """bang_word : maybe_bang_word"""
+    p[0] = ('bang_word', p[1][1])
+
+def p_token(p):
+    """token : TOKEN
+             | Fi"""
+    p[0] = p[1]
+
+def p_empty(p):
+    'empty :'
+    p[0] = None
+    
+# Error rule for syntax errors
+def p_error(p):
+    msg = []
+    w = msg.append
+    w('%r\n' % p)
+    w('followed by:\n')
+    for i in range(5):
+        n = yacc.token()
+        if not n:
+            break
+        w('  %r\n' % n)
+    raise sherrors.ShellSyntaxError(''.join(msg))
+
+# Build the parser
+try:
+    import pyshtables
+except ImportError:
+    outputdir = os.path.dirname(__file__)
+    if not os.access(outputdir, os.W_OK):
+        outputdir = ''
+    yacc.yacc(tabmodule = 'pyshtables', outputdir = outputdir, debug = 0)
+else:
+    yacc.yacc(tabmodule = 'pysh.pyshtables', write_tables = 0, debug = 0)
+
+
+def parse(input, eof=False, debug=False):
+    """Parse a whole script at once and return the generated AST and unconsumed
+    data in a tuple.
+    
+    NOTE: eof is probably meaningless for now, the parser being unable to work
+    in pull mode. It should be set to True.
+    """
+    lexer = pyshlex.PLYLexer()
+    remaining = lexer.add(input, eof)
+    if lexer.is_empty():
+        return [], remaining
+    if debug:
+        debug = 2
+    return yacc.parse(lexer=lexer, debug=debug), remaining
+
+#-------------------------------------------------------------------------------
+# AST rendering helpers
+#-------------------------------------------------------------------------------    
+
+def format_commands(v):
+    """Return a tree made of strings and lists. Make command trees easier to
+    display.
+    """
+    if isinstance(v, list):
+        return [format_commands(c) for c in v]
+    if isinstance(v, tuple):
+        if len(v)==2 and isinstance(v[0], str) and not isinstance(v[1], str):
+            if v[0] == 'async':
+                return ['AsyncList', map(format_commands, v[1])]
+            else:
+                #Avoid decomposing tuples like ('pipeline', Pipeline(...))
+                return format_commands(v[1])
+        return format_commands(list(v))
+    elif isinstance(v, IfCond):
+        name = ['IfCond']
+        name += ['if', map(format_commands, v.cond)]
+        name += ['then', map(format_commands, v.if_cmds)]
+        name += ['else', map(format_commands, v.else_cmds)]
+        return name
+    elif isinstance(v, ForLoop):
+        name = ['ForLoop']
+        name += [repr(v.name)+' in ', map(str, v.items)]
+        name += ['commands', map(format_commands, v.cmds)]
+        return name
+    elif isinstance(v, AndOr):
+        return [v.op, format_commands(v.left), format_commands(v.right)]
+    elif isinstance(v, Pipeline):
+        name = 'Pipeline'
+        if v.reverse_status:
+            name = '!' + name
+        return [name, format_commands(v.commands)]
+    elif isinstance(v, Case):
+        name = ['Case']
+        name += [v.name, format_commands(v.items)]
+    elif isinstance(v, SimpleCommand):
+        name = ['SimpleCommand']
+        if v.words:                
+            name += ['words', map(str, v.words)]
+        if v.assigns:
+            assigns = [tuple(a[1]) for a in v.assigns]
+            name += ['assigns', map(str, assigns)]
+        if v.redirs:
+            name += ['redirs', map(format_commands, v.redirs)]
+        return name
+    elif isinstance(v, RedirectList):
+        name = ['RedirectList']
+        if v.redirs:
+            name += ['redirs', map(format_commands, v.redirs)]
+        name += ['command', format_commands(v.cmd)]
+        return name
+    elif isinstance(v, IORedirect):
+        return ' '.join(map(str, (v.io_number, v.op, v.filename)))
+    elif isinstance(v, HereDocument):
+        return ' '.join(map(str, (v.io_number, v.op, repr(v.name), repr(v.content))))
+    elif isinstance(v, SubShell):
+        return ['SubShell', map(format_commands, v.cmds)]
+    else:
+        return repr(v)
+             
+def print_commands(cmds, output=sys.stdout):
+    """Pretty print a command tree."""
+    def print_tree(cmd, spaces, output):
+        if isinstance(cmd, list):
+            for c in cmd:
+                print_tree(c, spaces + 3, output)              
+        else:
+            print >>output, ' '*spaces + str(cmd)
+    
+    formatted = format_commands(cmds)
+    print_tree(formatted, 0, output)
+    
+    
+def stringify_commands(cmds): 
+    """Serialize a command tree as a string.
+    
+    Returned string is not pretty and is currently used for unit tests only.
+    """   
+    def stringify(value):
+        output = []
+        if isinstance(value, list):
+            formatted = []
+            for v in value:
+                formatted.append(stringify(v))
+            formatted = ' '.join(formatted)
+            output.append(''.join(['<', formatted, '>']))
+        else:
+            output.append(value)
+        return ' '.join(output)
+            
+    return stringify(format_commands(cmds))
+    
+        
+def visit_commands(cmds, callable):
+    """Visit the command tree and execute callable on every Pipeline and 
+    SimpleCommand instances.
+    """
+    if isinstance(cmds, (tuple, list)):
+        map(lambda c: visit_commands(c,callable), cmds)
+    elif isinstance(cmds, (Pipeline, SimpleCommand)):
+        callable(cmds)
diff --git a/bitbake/lib/bb/pysh/sherrors.py b/bitbake/lib/bb/pysh/sherrors.py
new file mode 100644
index 0000000..49d0533
--- /dev/null
+++ b/bitbake/lib/bb/pysh/sherrors.py
@@ -0,0 +1,41 @@
+# sherrors.py - shell errors and signals
+#
+# Copyright 2007 Patrick Mezard
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+"""Define shell exceptions and error codes.
+"""
+
+class ShellError(Exception):
+    pass
+
+class ShellSyntaxError(ShellError):
+    pass
+    
+class UtilityError(ShellError):
+    """Raised upon utility syntax error (option or operand error)."""
+    pass
+   
+class ExpansionError(ShellError):
+    pass
+     
+class CommandNotFound(ShellError):
+    """Specified command was not found."""
+    pass
+    
+class RedirectionError(ShellError):
+    pass
+    
+class VarAssignmentError(ShellError):
+    """Variable assignment error."""
+    pass
+    
+class ExitSignal(ShellError):
+    """Exit signal."""
+    pass
+    
+class ReturnSignal(ShellError):
+    """Exit signal."""
+    pass
diff --git a/bitbake/lib/bb/pysh/subprocess_fix.py b/bitbake/lib/bb/pysh/subprocess_fix.py
new file mode 100644
index 0000000..46eca22
--- /dev/null
+++ b/bitbake/lib/bb/pysh/subprocess_fix.py
@@ -0,0 +1,77 @@
+# subprocess - Subprocesses with accessible I/O streams
+#
+# For more information about this module, see PEP 324.
+#
+# This module should remain compatible with Python 2.2, see PEP 291.
+#
+# Copyright (c) 2003-2005 by Peter Astrand <astrand@lysator.liu.se>
+#
+# Licensed to PSF under a Contributor Agreement.
+# See http://www.python.org/2.4/license for licensing details.
+
+def list2cmdline(seq):
+    """
+    Translate a sequence of arguments into a command line
+    string, using the same rules as the MS C runtime:
+
+    1) Arguments are delimited by white space, which is either a
+       space or a tab.
+
+    2) A string surrounded by double quotation marks is
+       interpreted as a single argument, regardless of white space
+       contained within.  A quoted string can be embedded in an
+       argument.
+
+    3) A double quotation mark preceded by a backslash is
+       interpreted as a literal double quotation mark.
+
+    4) Backslashes are interpreted literally, unless they
+       immediately precede a double quotation mark.
+
+    5) If backslashes immediately precede a double quotation mark,
+       every pair of backslashes is interpreted as a literal
+       backslash.  If the number of backslashes is odd, the last
+       backslash escapes the next double quotation mark as
+       described in rule 3.
+    """
+
+    # See
+    # http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp
+    result = []
+    needquote = False
+    for arg in seq:
+        bs_buf = []
+
+        # Add a space to separate this argument from the others
+        if result:
+            result.append(' ')
+
+        needquote = (" " in arg) or ("\t" in arg) or ("|" in arg) or arg == ""
+        if needquote:
+            result.append('"')
+
+        for c in arg:
+            if c == '\\':
+                # Don't know if we need to double yet.
+                bs_buf.append(c)
+            elif c == '"':
+                # Double backspaces.
+                result.append('\\' * len(bs_buf)*2)
+                bs_buf = []
+                result.append('\\"')
+            else:
+                # Normal char
+                if bs_buf:
+                    result.extend(bs_buf)
+                    bs_buf = []
+                result.append(c)
+
+        # Add remaining backspaces, if any.
+        if bs_buf:
+            result.extend(bs_buf)
+
+        if needquote:
+            result.extend(bs_buf)
+            result.append('"')
+            
+    return ''.join(result)
diff --git a/bitbake/lib/bb/runqueue.py b/bitbake/lib/bb/runqueue.py
new file mode 100644
index 0000000..2b71eed
--- /dev/null
+++ b/bitbake/lib/bb/runqueue.py
@@ -0,0 +1,2195 @@
+#!/usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake 'RunQueue' implementation
+
+Handles preparation and execution of a queue of tasks
+"""
+
+# Copyright (C) 2006-2007  Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import copy
+import os
+import sys
+import signal
+import stat
+import fcntl
+import errno
+import logging
+import re
+import bb
+from bb import msg, data, event
+from bb import monitordisk
+import subprocess
+
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+
+bblogger = logging.getLogger("BitBake")
+logger = logging.getLogger("BitBake.RunQueue")
+
+__find_md5__ = re.compile( r'(?i)(?<![a-z0-9])[a-f0-9]{32}(?![a-z0-9])' )
+
+class RunQueueStats:
+    """
+    Holds statistics on the tasks handled by the associated runQueue
+    """
+    def __init__(self, total):
+        self.completed = 0
+        self.skipped = 0
+        self.failed = 0
+        self.active = 0
+        self.total = total
+
+    def copy(self):
+        obj = self.__class__(self.total)
+        obj.__dict__.update(self.__dict__)
+        return obj
+
+    def taskFailed(self):
+        self.active = self.active - 1
+        self.failed = self.failed + 1
+
+    def taskCompleted(self, number = 1):
+        self.active = self.active - number
+        self.completed = self.completed + number
+
+    def taskSkipped(self, number = 1):
+        self.active = self.active + number
+        self.skipped = self.skipped + number
+
+    def taskActive(self):
+        self.active = self.active + 1
+
+# These values indicate the next step due to be run in the
+# runQueue state machine
+runQueuePrepare = 2
+runQueueSceneInit = 3
+runQueueSceneRun = 4
+runQueueRunInit = 5
+runQueueRunning = 6
+runQueueFailed = 7
+runQueueCleanUp = 8
+runQueueComplete = 9
+
+class RunQueueScheduler(object):
+    """
+    Control the order tasks are scheduled in.
+    """
+    name = "basic"
+
+    def __init__(self, runqueue, rqdata):
+        """
+        The default scheduler just returns the first buildable task (the
+        priority map is sorted by task number)
+        """
+        self.rq = runqueue
+        self.rqdata = rqdata
+        self.numTasks = len(self.rqdata.runq_fnid)
+
+        self.prio_map = []
+        self.prio_map.extend(range(self.numTasks))
+
+        self.buildable = []
+        self.stamps = {}
+        for taskid in xrange(self.numTasks):
+            fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[taskid]]
+            taskname = self.rqdata.runq_task[taskid]
+            self.stamps[taskid] = bb.build.stampfile(taskname, self.rqdata.dataCache, fn)
+            if self.rq.runq_buildable[taskid] == 1:
+                self.buildable.append(taskid)
+
+        self.rev_prio_map = None
+
+    def next_buildable_task(self):
+        """
+        Return the id of the first task we find that is buildable
+        """
+        self.buildable = [x for x in self.buildable if not self.rq.runq_running[x] == 1]
+        if not self.buildable:
+            return None
+        if len(self.buildable) == 1:
+            taskid = self.buildable[0]
+            stamp = self.stamps[taskid]
+            if stamp not in self.rq.build_stamps.itervalues():
+                return taskid
+
+        if not self.rev_prio_map:
+            self.rev_prio_map = range(self.numTasks)
+            for taskid in xrange(self.numTasks):
+                self.rev_prio_map[self.prio_map[taskid]] = taskid
+
+        best = None
+        bestprio = None
+        for taskid in self.buildable:
+            prio = self.rev_prio_map[taskid]
+            if bestprio is None or bestprio > prio:
+                stamp = self.stamps[taskid]
+                if stamp in self.rq.build_stamps.itervalues():
+                    continue
+                bestprio = prio
+                best = taskid
+
+        return best
+
+    def next(self):
+        """
+        Return the id of the task we should build next
+        """
+        if self.rq.stats.active < self.rq.number_tasks:
+            return self.next_buildable_task()
+
+    def newbuilable(self, task):
+        self.buildable.append(task)
+
+class RunQueueSchedulerSpeed(RunQueueScheduler):
+    """
+    A scheduler optimised for speed. The priority map is sorted by task weight,
+    heavier weighted tasks (tasks needed by the most other tasks) are run first.
+    """
+    name = "speed"
+
+    def __init__(self, runqueue, rqdata):
+        """
+        The priority map is sorted by task weight.
+        """
+        RunQueueScheduler.__init__(self, runqueue, rqdata)
+
+        sortweight = sorted(copy.deepcopy(self.rqdata.runq_weight))
+        copyweight = copy.deepcopy(self.rqdata.runq_weight)
+        self.prio_map = []
+
+        for weight in sortweight:
+            idx = copyweight.index(weight)
+            self.prio_map.append(idx)
+            copyweight[idx] = -1
+
+        self.prio_map.reverse()
+
+class RunQueueSchedulerCompletion(RunQueueSchedulerSpeed):
+    """
+    A scheduler optimised to complete .bb files are quickly as possible. The
+    priority map is sorted by task weight, but then reordered so once a given
+    .bb file starts to build, it's completed as quickly as possible. This works
+    well where disk space is at a premium and classes like OE's rm_work are in
+    force.
+    """
+    name = "completion"
+
+    def __init__(self, runqueue, rqdata):
+        RunQueueSchedulerSpeed.__init__(self, runqueue, rqdata)
+
+        #FIXME - whilst this groups all fnids together it does not reorder the
+        #fnid groups optimally.
+
+        basemap = copy.deepcopy(self.prio_map)
+        self.prio_map = []
+        while (len(basemap) > 0):
+            entry = basemap.pop(0)
+            self.prio_map.append(entry)
+            fnid = self.rqdata.runq_fnid[entry]
+            todel = []
+            for entry in basemap:
+                entry_fnid = self.rqdata.runq_fnid[entry]
+                if entry_fnid == fnid:
+                    todel.append(basemap.index(entry))
+                    self.prio_map.append(entry)
+            todel.reverse()
+            for idx in todel:
+                del basemap[idx]
+
+class RunQueueData:
+    """
+    BitBake Run Queue implementation
+    """
+    def __init__(self, rq, cooker, cfgData, dataCache, taskData, targets):
+        self.cooker = cooker
+        self.dataCache = dataCache
+        self.taskData = taskData
+        self.targets = targets
+        self.rq = rq
+        self.warn_multi_bb = False
+
+        self.stampwhitelist = cfgData.getVar("BB_STAMP_WHITELIST", True) or ""
+        self.multi_provider_whitelist = (cfgData.getVar("MULTI_PROVIDER_WHITELIST", True) or "").split()
+
+        self.reset()
+
+    def reset(self):
+        self.runq_fnid = []
+        self.runq_task = []
+        self.runq_depends = []
+        self.runq_revdeps = []
+        self.runq_hash = []
+
+    def runq_depends_names(self, ids):
+        import re
+        ret = []
+        for id in self.runq_depends[ids]:
+            nam = os.path.basename(self.get_user_idstring(id))
+            nam = re.sub("_[^,]*,", ",", nam)
+            ret.extend([nam])
+        return ret
+
+    def get_task_name(self, task):
+        return self.runq_task[task]
+
+    def get_task_file(self, task):
+        return self.taskData.fn_index[self.runq_fnid[task]]
+
+    def get_task_hash(self, task):
+        return self.runq_hash[task]
+
+    def get_user_idstring(self, task, task_name_suffix = ""):
+        fn = self.taskData.fn_index[self.runq_fnid[task]]
+        taskname = self.runq_task[task] + task_name_suffix
+        return "%s, %s" % (fn, taskname)
+
+    def get_task_id(self, fnid, taskname):
+        for listid in xrange(len(self.runq_fnid)):
+            if self.runq_fnid[listid] == fnid and self.runq_task[listid] == taskname:
+                return listid
+        return None
+
+    def circular_depchains_handler(self, tasks):
+        """
+        Some tasks aren't buildable, likely due to circular dependency issues.
+        Identify the circular dependencies and print them in a user readable format.
+        """
+        from copy import deepcopy
+
+        valid_chains = []
+        explored_deps = {}
+        msgs = []
+
+        def chain_reorder(chain):
+            """
+            Reorder a dependency chain so the lowest task id is first
+            """
+            lowest = 0
+            new_chain = []
+            for entry in xrange(len(chain)):
+                if chain[entry] < chain[lowest]:
+                    lowest = entry
+            new_chain.extend(chain[lowest:])
+            new_chain.extend(chain[:lowest])
+            return new_chain
+
+        def chain_compare_equal(chain1, chain2):
+            """
+            Compare two dependency chains and see if they're the same
+            """
+            if len(chain1) != len(chain2):
+                return False
+            for index in xrange(len(chain1)):
+                if chain1[index] != chain2[index]:
+                    return False
+            return True
+
+        def chain_array_contains(chain, chain_array):
+            """
+            Return True if chain_array contains chain
+            """
+            for ch in chain_array:
+                if chain_compare_equal(ch, chain):
+                    return True
+            return False
+
+        def find_chains(taskid, prev_chain):
+            prev_chain.append(taskid)
+            total_deps = []
+            total_deps.extend(self.runq_revdeps[taskid])
+            for revdep in self.runq_revdeps[taskid]:
+                if revdep in prev_chain:
+                    idx = prev_chain.index(revdep)
+                    # To prevent duplicates, reorder the chain to start with the lowest taskid
+                    # and search through an array of those we've already printed
+                    chain = prev_chain[idx:]
+                    new_chain = chain_reorder(chain)
+                    if not chain_array_contains(new_chain, valid_chains):
+                        valid_chains.append(new_chain)
+                        msgs.append("Dependency loop #%d found:\n" % len(valid_chains))
+                        for dep in new_chain:
+                            msgs.append("  Task %s (%s) (dependent Tasks %s)\n" % (dep, self.get_user_idstring(dep), self.runq_depends_names(dep)))
+                        msgs.append("\n")
+                    if len(valid_chains) > 10:
+                        msgs.append("Aborted dependency loops search after 10 matches.\n")
+                        return msgs
+                    continue
+                scan = False
+                if revdep not in explored_deps:
+                    scan = True
+                elif revdep in explored_deps[revdep]:
+                    scan = True
+                else:
+                    for dep in prev_chain:
+                        if dep in explored_deps[revdep]:
+                            scan = True
+                if scan:
+                    find_chains(revdep, copy.deepcopy(prev_chain))
+                for dep in explored_deps[revdep]:
+                    if dep not in total_deps:
+                        total_deps.append(dep)
+
+            explored_deps[taskid] = total_deps
+
+        for task in tasks:
+            find_chains(task, [])
+
+        return msgs
+
+    def calculate_task_weights(self, endpoints):
+        """
+        Calculate a number representing the "weight" of each task. Heavier weighted tasks
+        have more dependencies and hence should be executed sooner for maximum speed.
+
+        This function also sanity checks the task list finding tasks that are not
+        possible to execute due to circular dependencies.
+        """
+
+        numTasks = len(self.runq_fnid)
+        weight = []
+        deps_left = []
+        task_done = []
+
+        for listid in xrange(numTasks):
+            task_done.append(False)
+            weight.append(1)
+            deps_left.append(len(self.runq_revdeps[listid]))
+
+        for listid in endpoints:
+            weight[listid] = 10
+            task_done[listid] = True
+
+        while True:
+            next_points = []
+            for listid in endpoints:
+                for revdep in self.runq_depends[listid]:
+                    weight[revdep] = weight[revdep] + weight[listid]
+                    deps_left[revdep] = deps_left[revdep] - 1
+                    if deps_left[revdep] == 0:
+                        next_points.append(revdep)
+                        task_done[revdep] = True
+            endpoints = next_points
+            if len(next_points) == 0:
+                break
+
+        # Circular dependency sanity check
+        problem_tasks = []
+        for task in xrange(numTasks):
+            if task_done[task] is False or deps_left[task] != 0:
+                problem_tasks.append(task)
+                logger.debug(2, "Task %s (%s) is not buildable", task, self.get_user_idstring(task))
+                logger.debug(2, "(Complete marker was %s and the remaining dependency count was %s)\n", task_done[task], deps_left[task])
+
+        if problem_tasks:
+            message = "Unbuildable tasks were found.\n"
+            message = message + "These are usually caused by circular dependencies and any circular dependency chains found will be printed below. Increase the debug level to see a list of unbuildable tasks.\n\n"
+            message = message + "Identifying dependency loops (this may take a short while)...\n"
+            logger.error(message)
+
+            msgs = self.circular_depchains_handler(problem_tasks)
+
+            message = "\n"
+            for msg in msgs:
+                message = message + msg
+            bb.msg.fatal("RunQueue", message)
+
+        return weight
+
+    def prepare(self):
+        """
+        Turn a set of taskData into a RunQueue and compute data needed
+        to optimise the execution order.
+        """
+
+        runq_build = []
+        recursivetasks = {}
+        recursiveitasks = {}
+        recursivetasksselfref = set()
+
+        taskData = self.taskData
+
+        if len(taskData.tasks_name) == 0:
+            # Nothing to do
+            return 0
+
+        logger.info("Preparing RunQueue")
+
+        # Step A - Work out a list of tasks to run
+        #
+        # Taskdata gives us a list of possible providers for every build and run
+        # target ordered by priority. It also gives information on each of those
+        # providers.
+        #
+        # To create the actual list of tasks to execute we fix the list of
+        # providers and then resolve the dependencies into task IDs. This
+        # process is repeated for each type of dependency (tdepends, deptask,
+        # rdeptast, recrdeptask, idepends).
+
+        def add_build_dependencies(depids, tasknames, depends):
+            for depid in depids:
+                # Won't be in build_targets if ASSUME_PROVIDED
+                if depid not in taskData.build_targets:
+                    continue
+                depdata = taskData.build_targets[depid][0]
+                if depdata is None:
+                    continue
+                for taskname in tasknames:
+                    taskid = taskData.gettask_id_fromfnid(depdata, taskname)
+                    if taskid is not None:
+                        depends.add(taskid)
+
+        def add_runtime_dependencies(depids, tasknames, depends):
+            for depid in depids:
+                if depid not in taskData.run_targets:
+                    continue
+                depdata = taskData.run_targets[depid][0]
+                if depdata is None:
+                    continue
+                for taskname in tasknames:
+                    taskid = taskData.gettask_id_fromfnid(depdata, taskname)
+                    if taskid is not None:
+                        depends.add(taskid)
+
+        def add_resolved_dependencies(depids, tasknames, depends):
+            for depid in depids:
+                for taskname in tasknames:
+                    taskid = taskData.gettask_id_fromfnid(depid, taskname)
+                    if taskid is not None:
+                        depends.add(taskid)
+
+        for task in xrange(len(taskData.tasks_name)):
+            depends = set()
+            fnid = taskData.tasks_fnid[task]
+            fn = taskData.fn_index[fnid]
+            task_deps = self.dataCache.task_deps[fn]
+
+            #logger.debug(2, "Processing %s:%s", fn, taskData.tasks_name[task])
+
+            if fnid not in taskData.failed_fnids:
+
+                # Resolve task internal dependencies
+                #
+                # e.g. addtask before X after Y
+                depends = set(taskData.tasks_tdepends[task])
+
+                # Resolve 'deptask' dependencies
+                #
+                # e.g. do_sometask[deptask] = "do_someothertask"
+                # (makes sure sometask runs after someothertask of all DEPENDS)
+                if 'deptask' in task_deps and taskData.tasks_name[task] in task_deps['deptask']:
+                    tasknames = task_deps['deptask'][taskData.tasks_name[task]].split()
+                    add_build_dependencies(taskData.depids[fnid], tasknames, depends)
+
+                # Resolve 'rdeptask' dependencies
+                #
+                # e.g. do_sometask[rdeptask] = "do_someothertask"
+                # (makes sure sometask runs after someothertask of all RDEPENDS)
+                if 'rdeptask' in task_deps and taskData.tasks_name[task] in task_deps['rdeptask']:
+                    tasknames = task_deps['rdeptask'][taskData.tasks_name[task]].split()
+                    add_runtime_dependencies(taskData.rdepids[fnid], tasknames, depends)
+
+                # Resolve inter-task dependencies
+                #
+                # e.g. do_sometask[depends] = "targetname:do_someothertask"
+                # (makes sure sometask runs after targetname's someothertask)
+                idepends = taskData.tasks_idepends[task]
+                for (depid, idependtask) in idepends:
+                    if depid in taskData.build_targets and not depid in taskData.failed_deps:
+                        # Won't be in build_targets if ASSUME_PROVIDED
+                        depdata = taskData.build_targets[depid][0]
+                        if depdata is not None:
+                            taskid = taskData.gettask_id_fromfnid(depdata, idependtask)
+                            if taskid is None:
+                                bb.msg.fatal("RunQueue", "Task %s in %s depends upon non-existent task %s in %s" % (taskData.tasks_name[task], fn, idependtask, taskData.fn_index[depdata]))
+                            depends.add(taskid)
+                irdepends = taskData.tasks_irdepends[task]
+                for (depid, idependtask) in irdepends:
+                    if depid in taskData.run_targets:
+                        # Won't be in run_targets if ASSUME_PROVIDED
+                        depdata = taskData.run_targets[depid][0]
+                        if depdata is not None:
+                            taskid = taskData.gettask_id_fromfnid(depdata, idependtask)
+                            if taskid is None:
+                                bb.msg.fatal("RunQueue", "Task %s in %s rdepends upon non-existent task %s in %s" % (taskData.tasks_name[task], fn, idependtask, taskData.fn_index[depdata]))
+                            depends.add(taskid)
+
+                # Resolve recursive 'recrdeptask' dependencies (Part A)
+                #
+                # e.g. do_sometask[recrdeptask] = "do_someothertask"
+                # (makes sure sometask runs after someothertask of all DEPENDS, RDEPENDS and intertask dependencies, recursively)
+                # We cover the recursive part of the dependencies below
+                if 'recrdeptask' in task_deps and taskData.tasks_name[task] in task_deps['recrdeptask']:
+                    tasknames = task_deps['recrdeptask'][taskData.tasks_name[task]].split()
+                    recursivetasks[task] = tasknames
+                    add_build_dependencies(taskData.depids[fnid], tasknames, depends)
+                    add_runtime_dependencies(taskData.rdepids[fnid], tasknames, depends)
+                    if taskData.tasks_name[task] in tasknames:
+                        recursivetasksselfref.add(task)
+
+                    if 'recideptask' in task_deps and taskData.tasks_name[task] in task_deps['recideptask']:
+                        recursiveitasks[task] = []
+                        for t in task_deps['recideptask'][taskData.tasks_name[task]].split():
+                            newdep = taskData.gettask_id_fromfnid(fnid, t)
+                            recursiveitasks[task].append(newdep)
+
+            self.runq_fnid.append(taskData.tasks_fnid[task])
+            self.runq_task.append(taskData.tasks_name[task])
+            self.runq_depends.append(depends)
+            self.runq_revdeps.append(set())
+            self.runq_hash.append("")
+
+            runq_build.append(0)
+
+        # Resolve recursive 'recrdeptask' dependencies (Part B)
+        #
+        # e.g. do_sometask[recrdeptask] = "do_someothertask"
+        # (makes sure sometask runs after someothertask of all DEPENDS, RDEPENDS and intertask dependencies, recursively)
+        # We need to do this separately since we need all of self.runq_depends to be complete before this is processed
+        extradeps = {}
+        for task in recursivetasks:
+            extradeps[task] = set(self.runq_depends[task])
+            tasknames = recursivetasks[task]
+            seendeps = set()
+            seenfnid = []
+
+            def generate_recdeps(t):
+                newdeps = set()
+                add_resolved_dependencies([taskData.tasks_fnid[t]], tasknames, newdeps)
+                extradeps[task].update(newdeps)
+                seendeps.add(t)
+                newdeps.add(t)
+                for i in newdeps:
+                    for n in self.runq_depends[i]:
+                        if n not in seendeps:
+                            generate_recdeps(n)
+            generate_recdeps(task)
+
+            if task in recursiveitasks:
+                for dep in recursiveitasks[task]:
+                    generate_recdeps(dep)
+
+        # Remove circular references so that do_a[recrdeptask] = "do_a do_b" can work
+        for task in recursivetasks:
+            extradeps[task].difference_update(recursivetasksselfref)
+
+        for task in xrange(len(taskData.tasks_name)):
+            # Add in extra dependencies
+            if task in extradeps:
+                 self.runq_depends[task] = extradeps[task]
+            # Remove all self references
+            if task in self.runq_depends[task]:
+                logger.debug(2, "Task %s (%s %s) contains self reference! %s", task, taskData.fn_index[taskData.tasks_fnid[task]], taskData.tasks_name[task], self.runq_depends[task])
+                self.runq_depends[task].remove(task)
+
+        # Step B - Mark all active tasks
+        #
+        # Start with the tasks we were asked to run and mark all dependencies
+        # as active too. If the task is to be 'forced', clear its stamp. Once
+        # all active tasks are marked, prune the ones we don't need.
+
+        logger.verbose("Marking Active Tasks")
+
+        def mark_active(listid, depth):
+            """
+            Mark an item as active along with its depends
+            (calls itself recursively)
+            """
+
+            if runq_build[listid] == 1:
+                return
+
+            runq_build[listid] = 1
+
+            depends = self.runq_depends[listid]
+            for depend in depends:
+                mark_active(depend, depth+1)
+
+        self.target_pairs = []
+        for target in self.targets:
+            targetid = taskData.getbuild_id(target[0])
+
+            if targetid not in taskData.build_targets:
+                continue
+
+            if targetid in taskData.failed_deps:
+                continue
+
+            fnid = taskData.build_targets[targetid][0]
+            fn = taskData.fn_index[fnid]
+            self.target_pairs.append((fn, target[1]))
+
+            if fnid in taskData.failed_fnids:
+                continue
+
+            if target[1] not in taskData.tasks_lookup[fnid]:
+                import difflib
+                close_matches = difflib.get_close_matches(target[1], taskData.tasks_lookup[fnid], cutoff=0.7)
+                if close_matches:
+                    extra = ". Close matches:\n  %s" % "\n  ".join(close_matches)
+                else:
+                    extra = ""
+                bb.msg.fatal("RunQueue", "Task %s does not exist for target %s%s" % (target[1], target[0], extra))
+
+            listid = taskData.tasks_lookup[fnid][target[1]]
+
+            mark_active(listid, 1)
+
+        # Step C - Prune all inactive tasks
+        #
+        # Once all active tasks are marked, prune the ones we don't need.
+
+        maps = []
+        delcount = 0
+        for listid in xrange(len(self.runq_fnid)):
+            if runq_build[listid-delcount] == 1:
+                maps.append(listid-delcount)
+            else:
+                del self.runq_fnid[listid-delcount]
+                del self.runq_task[listid-delcount]
+                del self.runq_depends[listid-delcount]
+                del runq_build[listid-delcount]
+                del self.runq_revdeps[listid-delcount]
+                del self.runq_hash[listid-delcount]
+                delcount = delcount + 1
+                maps.append(-1)
+
+        #
+        # Step D - Sanity checks and computation
+        #
+
+        # Check to make sure we still have tasks to run
+        if len(self.runq_fnid) == 0:
+            if not taskData.abort:
+                bb.msg.fatal("RunQueue", "All buildable tasks have been run but the build is incomplete (--continue mode). Errors for the tasks that failed will have been printed above.")
+            else:
+                bb.msg.fatal("RunQueue", "No active tasks and not in --continue mode?! Please report this bug.")
+
+        logger.verbose("Pruned %s inactive tasks, %s left", delcount, len(self.runq_fnid))
+
+        # Remap the dependencies to account for the deleted tasks
+        # Check we didn't delete a task we depend on
+        for listid in xrange(len(self.runq_fnid)):
+            newdeps = []
+            origdeps = self.runq_depends[listid]
+            for origdep in origdeps:
+                if maps[origdep] == -1:
+                    bb.msg.fatal("RunQueue", "Invalid mapping - Should never happen!")
+                newdeps.append(maps[origdep])
+            self.runq_depends[listid] = set(newdeps)
+
+        logger.verbose("Assign Weightings")
+
+        # Generate a list of reverse dependencies to ease future calculations
+        for listid in xrange(len(self.runq_fnid)):
+            for dep in self.runq_depends[listid]:
+                self.runq_revdeps[dep].add(listid)
+
+        # Identify tasks at the end of dependency chains
+        # Error on circular dependency loops (length two)
+        endpoints = []
+        for listid in xrange(len(self.runq_fnid)):
+            revdeps = self.runq_revdeps[listid]
+            if len(revdeps) == 0:
+                endpoints.append(listid)
+            for dep in revdeps:
+                if dep in self.runq_depends[listid]:
+                    #self.dump_data(taskData)
+                    bb.msg.fatal("RunQueue", "Task %s (%s) has circular dependency on %s (%s)" % (taskData.fn_index[self.runq_fnid[dep]], self.runq_task[dep], taskData.fn_index[self.runq_fnid[listid]], self.runq_task[listid]))
+
+        logger.verbose("Compute totals (have %s endpoint(s))", len(endpoints))
+
+        # Calculate task weights
+        # Check of higher length circular dependencies
+        self.runq_weight = self.calculate_task_weights(endpoints)
+
+        # Sanity Check - Check for multiple tasks building the same provider
+        prov_list = {}
+        seen_fn = []
+        for task in xrange(len(self.runq_fnid)):
+            fn = taskData.fn_index[self.runq_fnid[task]]
+            if fn in seen_fn:
+                continue
+            seen_fn.append(fn)
+            for prov in self.dataCache.fn_provides[fn]:
+                if prov not in prov_list:
+                    prov_list[prov] = [fn]
+                elif fn not in prov_list[prov]:
+                    prov_list[prov].append(fn)
+        for prov in prov_list:
+            if len(prov_list[prov]) > 1 and prov not in self.multi_provider_whitelist:
+                seen_pn = []
+                # If two versions of the same PN are being built its fatal, we don't support it.
+                for fn in prov_list[prov]:
+                    pn = self.dataCache.pkg_fn[fn]
+                    if pn not in seen_pn:
+                        seen_pn.append(pn)
+                    else:
+                        bb.fatal("Multiple versions of %s are due to be built (%s). Only one version of a given PN should be built in any given build. You likely need to set PREFERRED_VERSION_%s to select the correct version or don't depend on multiple versions." % (pn, " ".join(prov_list[prov]), pn))
+                msg = "Multiple .bb files are due to be built which each provide %s (%s)." % (prov, " ".join(prov_list[prov]))
+                if self.warn_multi_bb:
+                    logger.warn(msg)
+                else:
+                    msg += "\n This usually means one provides something the other doesn't and should."
+                    logger.error(msg)
+
+        # Create a whitelist usable by the stamp checks
+        stampfnwhitelist = []
+        for entry in self.stampwhitelist.split():
+            entryid = self.taskData.getbuild_id(entry)
+            if entryid not in self.taskData.build_targets:
+                continue
+            fnid = self.taskData.build_targets[entryid][0]
+            fn = self.taskData.fn_index[fnid]
+            stampfnwhitelist.append(fn)
+        self.stampfnwhitelist = stampfnwhitelist
+
+        # Iterate over the task list looking for tasks with a 'setscene' function
+        self.runq_setscene = []
+        if not self.cooker.configuration.nosetscene:
+            for task in range(len(self.runq_fnid)):
+                setscene = taskData.gettask_id(self.taskData.fn_index[self.runq_fnid[task]], self.runq_task[task] + "_setscene", False)
+                if not setscene:
+                    continue
+                self.runq_setscene.append(task)
+
+        def invalidate_task(fn, taskname, error_nostamp):
+            taskdep = self.dataCache.task_deps[fn]
+            fnid = self.taskData.getfn_id(fn)
+            if taskname not in taskData.tasks_lookup[fnid]:
+                logger.warn("Task %s does not exist, invalidating this task will have no effect" % taskname)
+            if 'nostamp' in taskdep and taskname in taskdep['nostamp']:
+                if error_nostamp:
+                    bb.fatal("Task %s is marked nostamp, cannot invalidate this task" % taskname)
+                else:
+                    bb.debug(1, "Task %s is marked nostamp, cannot invalidate this task" % taskname)
+            else:
+                logger.verbose("Invalidate task %s, %s", taskname, fn)
+                bb.parse.siggen.invalidate_task(taskname, self.dataCache, fn)
+
+        # Invalidate task if force mode active
+        if self.cooker.configuration.force:
+            for (fn, target) in self.target_pairs:
+                invalidate_task(fn, target, False)
+
+        # Invalidate task if invalidate mode active
+        if self.cooker.configuration.invalidate_stamp:
+            for (fn, target) in self.target_pairs:
+                for st in self.cooker.configuration.invalidate_stamp.split(','):
+                    if not st.startswith("do_"):
+                        st = "do_%s" % st
+                    invalidate_task(fn, st, True)
+
+        # Iterate over the task list and call into the siggen code
+        dealtwith = set()
+        todeal = set(range(len(self.runq_fnid)))
+        while len(todeal) > 0:
+            for task in todeal.copy():
+                if len(self.runq_depends[task] - dealtwith) == 0:
+                    dealtwith.add(task)
+                    todeal.remove(task)
+                    procdep = []
+                    for dep in self.runq_depends[task]:
+                        procdep.append(self.taskData.fn_index[self.runq_fnid[dep]] + "." + self.runq_task[dep])
+                    self.runq_hash[task] = bb.parse.siggen.get_taskhash(self.taskData.fn_index[self.runq_fnid[task]], self.runq_task[task], procdep, self.dataCache)
+
+        return len(self.runq_fnid)
+
+    def dump_data(self, taskQueue):
+        """
+        Dump some debug information on the internal data structures
+        """
+        logger.debug(3, "run_tasks:")
+        for task in xrange(len(self.rqdata.runq_task)):
+            logger.debug(3, " (%s)%s - %s: %s   Deps %s RevDeps %s", task,
+                         taskQueue.fn_index[self.rqdata.runq_fnid[task]],
+                         self.rqdata.runq_task[task],
+                         self.rqdata.runq_weight[task],
+                         self.rqdata.runq_depends[task],
+                         self.rqdata.runq_revdeps[task])
+
+        logger.debug(3, "sorted_tasks:")
+        for task1 in xrange(len(self.rqdata.runq_task)):
+            if task1 in self.prio_map:
+                task = self.prio_map[task1]
+                logger.debug(3, " (%s)%s - %s: %s   Deps %s RevDeps %s", task,
+                           taskQueue.fn_index[self.rqdata.runq_fnid[task]],
+                           self.rqdata.runq_task[task],
+                           self.rqdata.runq_weight[task],
+                           self.rqdata.runq_depends[task],
+                           self.rqdata.runq_revdeps[task])
+
+class RunQueue:
+    def __init__(self, cooker, cfgData, dataCache, taskData, targets):
+
+        self.cooker = cooker
+        self.cfgData = cfgData
+        self.rqdata = RunQueueData(self, cooker, cfgData, dataCache, taskData, targets)
+
+        self.stamppolicy = cfgData.getVar("BB_STAMP_POLICY", True) or "perfile"
+        self.hashvalidate = cfgData.getVar("BB_HASHCHECK_FUNCTION", True) or None
+        self.setsceneverify = cfgData.getVar("BB_SETSCENE_VERIFY_FUNCTION", True) or None
+        self.depvalidate = cfgData.getVar("BB_SETSCENE_DEPVALID", True) or None
+
+        self.state = runQueuePrepare
+
+        # For disk space monitor
+        self.dm = monitordisk.diskMonitor(cfgData)
+
+        self.rqexe = None
+        self.worker = None
+        self.workerpipe = None
+        self.fakeworker = None
+        self.fakeworkerpipe = None
+
+    def _start_worker(self, fakeroot = False, rqexec = None):
+        logger.debug(1, "Starting bitbake-worker")
+        magic = "decafbad"
+        if self.cooker.configuration.profile:
+            magic = "decafbadbad"
+        if fakeroot:
+            fakerootcmd = self.cfgData.getVar("FAKEROOTCMD", True)
+            fakerootenv = (self.cfgData.getVar("FAKEROOTBASEENV", True) or "").split()
+            env = os.environ.copy()
+            for key, value in (var.split('=') for var in fakerootenv):
+                env[key] = value
+            worker = subprocess.Popen([fakerootcmd, "bitbake-worker", magic], stdout=subprocess.PIPE, stdin=subprocess.PIPE, env=env)
+        else:
+            worker = subprocess.Popen(["bitbake-worker", magic], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
+        bb.utils.nonblockingfd(worker.stdout)
+        workerpipe = runQueuePipe(worker.stdout, None, self.cfgData, self, rqexec)
+
+        workerdata = {
+            "taskdeps" : self.rqdata.dataCache.task_deps,
+            "fakerootenv" : self.rqdata.dataCache.fakerootenv,
+            "fakerootdirs" : self.rqdata.dataCache.fakerootdirs,
+            "fakerootnoenv" : self.rqdata.dataCache.fakerootnoenv,
+            "sigdata" : bb.parse.siggen.get_taskdata(),
+            "runq_hash" : self.rqdata.runq_hash,
+            "logdefaultdebug" : bb.msg.loggerDefaultDebugLevel,
+            "logdefaultverbose" : bb.msg.loggerDefaultVerbose,
+            "logdefaultverboselogs" : bb.msg.loggerVerboseLogs,
+            "logdefaultdomain" : bb.msg.loggerDefaultDomains,
+            "prhost" : self.cooker.prhost,
+            "buildname" : self.cfgData.getVar("BUILDNAME", True),
+            "date" : self.cfgData.getVar("DATE", True),
+            "time" : self.cfgData.getVar("TIME", True),
+        }
+
+        worker.stdin.write("<cookerconfig>" + pickle.dumps(self.cooker.configuration) + "</cookerconfig>")
+        worker.stdin.write("<workerdata>" + pickle.dumps(workerdata) + "</workerdata>")
+        worker.stdin.flush()
+
+        return worker, workerpipe
+
+    def _teardown_worker(self, worker, workerpipe):
+        if not worker:
+            return
+        logger.debug(1, "Teardown for bitbake-worker")
+        try:
+           worker.stdin.write("<quit></quit>")
+           worker.stdin.flush()
+        except IOError:
+           pass
+        while worker.returncode is None:
+            workerpipe.read()
+            worker.poll()
+        while workerpipe.read():
+            continue
+        workerpipe.close()
+
+    def start_worker(self):
+        if self.worker:
+            self.teardown_workers()
+        self.teardown = False
+        self.worker, self.workerpipe = self._start_worker()
+
+    def start_fakeworker(self, rqexec):
+        if not self.fakeworker:
+            self.fakeworker, self.fakeworkerpipe = self._start_worker(True, rqexec)
+
+    def teardown_workers(self):
+        self.teardown = True
+        self._teardown_worker(self.worker, self.workerpipe)
+        self.worker = None
+        self.workerpipe = None
+        self._teardown_worker(self.fakeworker, self.fakeworkerpipe)
+        self.fakeworker = None
+        self.fakeworkerpipe = None
+
+    def read_workers(self):
+        self.workerpipe.read()
+        if self.fakeworkerpipe:
+            self.fakeworkerpipe.read()
+
+    def active_fds(self):
+        fds = []
+        if self.workerpipe:
+            fds.append(self.workerpipe.input)
+        if self.fakeworkerpipe:
+            fds.append(self.fakeworkerpipe.input)
+        return fds
+
+    def check_stamp_task(self, task, taskname = None, recurse = False, cache = None):
+        def get_timestamp(f):
+            try:
+                if not os.access(f, os.F_OK):
+                    return None
+                return os.stat(f)[stat.ST_MTIME]
+            except:
+                return None
+
+        if self.stamppolicy == "perfile":
+            fulldeptree = False
+        else:
+            fulldeptree = True
+            stampwhitelist = []
+            if self.stamppolicy == "whitelist":
+                stampwhitelist = self.rqdata.stampfnwhitelist
+
+        fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[task]]
+        if taskname is None:
+            taskname = self.rqdata.runq_task[task]
+
+        stampfile = bb.build.stampfile(taskname, self.rqdata.dataCache, fn)
+
+        # If the stamp is missing, it's not current
+        if not os.access(stampfile, os.F_OK):
+            logger.debug(2, "Stampfile %s not available", stampfile)
+            return False
+        # If it's a 'nostamp' task, it's not current
+        taskdep = self.rqdata.dataCache.task_deps[fn]
+        if 'nostamp' in taskdep and taskname in taskdep['nostamp']:
+            logger.debug(2, "%s.%s is nostamp\n", fn, taskname)
+            return False
+
+        if taskname != "do_setscene" and taskname.endswith("_setscene"):
+            return True
+
+        if cache is None:
+            cache = {}
+
+        iscurrent = True
+        t1 = get_timestamp(stampfile)
+        for dep in self.rqdata.runq_depends[task]:
+            if iscurrent:
+                fn2 = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[dep]]
+                taskname2 = self.rqdata.runq_task[dep]
+                stampfile2 = bb.build.stampfile(taskname2, self.rqdata.dataCache, fn2)
+                stampfile3 = bb.build.stampfile(taskname2 + "_setscene", self.rqdata.dataCache, fn2)
+                t2 = get_timestamp(stampfile2)
+                t3 = get_timestamp(stampfile3)
+                if t3 and t3 > t2:
+                   continue
+                if fn == fn2 or (fulldeptree and fn2 not in stampwhitelist):
+                    if not t2:
+                        logger.debug(2, 'Stampfile %s does not exist', stampfile2)
+                        iscurrent = False
+                    if t1 < t2:
+                        logger.debug(2, 'Stampfile %s < %s', stampfile, stampfile2)
+                        iscurrent = False
+                    if recurse and iscurrent:
+                        if dep in cache:
+                            iscurrent = cache[dep]
+                            if not iscurrent:
+                                logger.debug(2, 'Stampfile for dependency %s:%s invalid (cached)' % (fn2, taskname2))
+                        else:
+                            iscurrent = self.check_stamp_task(dep, recurse=True, cache=cache)
+                            cache[dep] = iscurrent
+        if recurse:
+            cache[task] = iscurrent
+        return iscurrent
+
+    def _execute_runqueue(self):
+        """
+        Run the tasks in a queue prepared by rqdata.prepare()
+        Upon failure, optionally try to recover the build using any alternate providers
+        (if the abort on failure configuration option isn't set)
+        """
+
+        retval = True
+
+        if self.state is runQueuePrepare:
+            self.rqexe = RunQueueExecuteDummy(self)
+            if self.rqdata.prepare() == 0:
+                self.state = runQueueComplete
+            else:
+                self.state = runQueueSceneInit
+
+                # we are ready to run, see if any UI client needs the dependency info
+                if bb.cooker.CookerFeatures.SEND_DEPENDS_TREE in self.cooker.featureset:
+                    depgraph = self.cooker.buildDependTree(self, self.rqdata.taskData)
+                    bb.event.fire(bb.event.DepTreeGenerated(depgraph), self.cooker.data)
+
+        if self.state is runQueueSceneInit:
+            dump = self.cooker.configuration.dump_signatures
+            if dump:
+                if 'printdiff' in dump:
+                    invalidtasks = self.print_diffscenetasks()
+                self.dump_signatures(dump)
+                if 'printdiff' in dump:
+                    self.write_diffscenetasks(invalidtasks)
+                self.state = runQueueComplete
+            else:
+                self.start_worker()
+                self.rqexe = RunQueueExecuteScenequeue(self)
+
+        if self.state in [runQueueSceneRun, runQueueRunning, runQueueCleanUp]:
+            self.dm.check(self)
+
+        if self.state is runQueueSceneRun:
+            retval = self.rqexe.execute()
+
+        if self.state is runQueueRunInit:
+            logger.info("Executing RunQueue Tasks")
+            self.rqexe = RunQueueExecuteTasks(self)
+            self.state = runQueueRunning
+
+        if self.state is runQueueRunning:
+            retval = self.rqexe.execute()
+
+        if self.state is runQueueCleanUp:
+            retval = self.rqexe.finish()
+
+        if (self.state is runQueueComplete or self.state is runQueueFailed) and self.rqexe:
+            self.teardown_workers()
+            if self.rqexe.stats.failed:
+                logger.info("Tasks Summary: Attempted %d tasks of which %d didn't need to be rerun and %d failed.", self.rqexe.stats.completed + self.rqexe.stats.failed, self.rqexe.stats.skipped, self.rqexe.stats.failed)
+            else:
+                # Let's avoid the word "failed" if nothing actually did
+                logger.info("Tasks Summary: Attempted %d tasks of which %d didn't need to be rerun and all succeeded.", self.rqexe.stats.completed, self.rqexe.stats.skipped)
+
+        if self.state is runQueueFailed:
+            if not self.rqdata.taskData.tryaltconfigs:
+                raise bb.runqueue.TaskFailure(self.rqexe.failed_fnids)
+            for fnid in self.rqexe.failed_fnids:
+                self.rqdata.taskData.fail_fnid(fnid)
+            self.rqdata.reset()
+
+        if self.state is runQueueComplete:
+            # All done
+            return False
+
+        # Loop
+        return retval
+
+    def execute_runqueue(self):
+        # Catch unexpected exceptions and ensure we exit when an error occurs, not loop.
+        try:
+            return self._execute_runqueue()
+        except bb.runqueue.TaskFailure:
+            raise
+        except SystemExit:
+            raise
+        except bb.BBHandledException:
+            try:
+                self.teardown_workers()
+            except:
+                pass
+            self.state = runQueueComplete
+            raise
+        except:
+            logger.error("An uncaught exception occured in runqueue, please see the failure below:")
+            try:
+                self.teardown_workers()
+            except:
+                pass
+            self.state = runQueueComplete
+            raise
+
+    def finish_runqueue(self, now = False):
+        if not self.rqexe:
+            self.state = runQueueComplete
+            return
+
+        if now:
+            self.rqexe.finish_now()
+        else:
+            self.rqexe.finish()
+
+    def dump_signatures(self, options):
+        done = set()
+        bb.note("Reparsing files to collect dependency data")
+        for task in range(len(self.rqdata.runq_fnid)):
+            if self.rqdata.runq_fnid[task] not in done:
+                fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[task]]
+                the_data = bb.cache.Cache.loadDataFull(fn, self.cooker.collection.get_file_appends(fn), self.cooker.data)
+                done.add(self.rqdata.runq_fnid[task])
+
+        bb.parse.siggen.dump_sigs(self.rqdata.dataCache, options)
+
+        return
+
+    def print_diffscenetasks(self):
+
+        valid = []
+        sq_hash = []
+        sq_hashfn = []
+        sq_fn = []
+        sq_taskname = []
+        sq_task = []
+        noexec = []
+        stamppresent = []
+        valid_new = set()
+
+        for task in xrange(len(self.rqdata.runq_fnid)):
+            fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[task]]
+            taskname = self.rqdata.runq_task[task]
+            taskdep = self.rqdata.dataCache.task_deps[fn]
+
+            if 'noexec' in taskdep and taskname in taskdep['noexec']:
+                noexec.append(task)
+                continue
+
+            sq_fn.append(fn)
+            sq_hashfn.append(self.rqdata.dataCache.hashfn[fn])
+            sq_hash.append(self.rqdata.runq_hash[task])
+            sq_taskname.append(taskname)
+            sq_task.append(task)
+        locs = { "sq_fn" : sq_fn, "sq_task" : sq_taskname, "sq_hash" : sq_hash, "sq_hashfn" : sq_hashfn, "d" : self.cooker.expanded_data }
+        try:
+            call = self.hashvalidate + "(sq_fn, sq_task, sq_hash, sq_hashfn, d, siginfo=True)"
+            valid = bb.utils.better_eval(call, locs)
+        # Handle version with no siginfo parameter
+        except TypeError:
+            call = self.hashvalidate + "(sq_fn, sq_task, sq_hash, sq_hashfn, d)"
+            valid = bb.utils.better_eval(call, locs)
+        for v in valid:
+            valid_new.add(sq_task[v])
+
+        # Tasks which are both setscene and noexec never care about dependencies
+        # We therefore find tasks which are setscene and noexec and mark their
+        # unique dependencies as valid.
+        for task in noexec:
+            if task not in self.rqdata.runq_setscene:
+                continue
+            for dep in self.rqdata.runq_depends[task]:
+                hasnoexecparents = True
+                for dep2 in self.rqdata.runq_revdeps[dep]:
+                    if dep2 in self.rqdata.runq_setscene and dep2 in noexec:
+                        continue
+                    hasnoexecparents = False
+                    break
+                if hasnoexecparents:
+                    valid_new.add(dep)
+
+        invalidtasks = set()
+        for task in xrange(len(self.rqdata.runq_fnid)):
+            if task not in valid_new and task not in noexec:
+                invalidtasks.add(task)
+
+        found = set()
+        processed = set()
+        for task in invalidtasks:
+            toprocess = set([task])
+            while toprocess:
+                next = set()
+                for t in toprocess:
+                    for dep in self.rqdata.runq_depends[t]:
+                        if dep in invalidtasks:
+                            found.add(task)
+                        if dep not in processed:
+                            processed.add(dep)
+                            next.add(dep)
+                toprocess = next
+                if task in found:
+                    toprocess = set()
+
+        tasklist = []
+        for task in invalidtasks.difference(found):
+            tasklist.append(self.rqdata.get_user_idstring(task))
+
+        if tasklist:
+            bb.plain("The differences between the current build and any cached tasks start at the following tasks:\n" + "\n".join(tasklist))
+
+        return invalidtasks.difference(found)
+
+    def write_diffscenetasks(self, invalidtasks):
+
+        # Define recursion callback
+        def recursecb(key, hash1, hash2):
+            hashes = [hash1, hash2]
+            hashfiles = bb.siggen.find_siginfo(key, None, hashes, self.cfgData)
+
+            recout = []
+            if len(hashfiles) == 2:
+                out2 = bb.siggen.compare_sigfiles(hashfiles[hash1], hashfiles[hash2], recursecb)
+                recout.extend(list('  ' + l for l in out2))
+            else:
+                recout.append("Unable to find matching sigdata for %s with hashes %s or %s" % (key, hash1, hash2))
+
+            return recout
+
+
+        for task in invalidtasks:
+            fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[task]]
+            pn = self.rqdata.dataCache.pkg_fn[fn]
+            taskname = self.rqdata.runq_task[task]
+            h = self.rqdata.runq_hash[task]
+            matches = bb.siggen.find_siginfo(pn, taskname, [], self.cfgData)
+            match = None
+            for m in matches:
+                if h in m:
+                    match = m
+            if match is None:
+                bb.fatal("Can't find a task we're supposed to have written out? (hash: %s)?" % h)
+            matches = {k : v for k, v in matches.iteritems() if h not in k}
+            if matches:
+                latestmatch = sorted(matches.keys(), key=lambda f: matches[f])[-1]
+                prevh = __find_md5__.search(latestmatch).group(0)
+                output = bb.siggen.compare_sigfiles(latestmatch, match, recursecb)
+                bb.plain("\nTask %s:%s couldn't be used from the cache because:\n  We need hash %s, closest matching task was %s\n  " % (pn, taskname, h, prevh) + '\n  '.join(output))
+
+class RunQueueExecute:
+
+    def __init__(self, rq):
+        self.rq = rq
+        self.cooker = rq.cooker
+        self.cfgData = rq.cfgData
+        self.rqdata = rq.rqdata
+
+        self.number_tasks = int(self.cfgData.getVar("BB_NUMBER_THREADS", True) or 1)
+        self.scheduler = self.cfgData.getVar("BB_SCHEDULER", True) or "speed"
+
+        self.runq_buildable = []
+        self.runq_running = []
+        self.runq_complete = []
+
+        self.build_stamps = {}
+        self.build_stamps2 = []
+        self.failed_fnids = []
+
+        self.stampcache = {}
+
+        rq.workerpipe.setrunqueueexec(self)
+        if rq.fakeworkerpipe:
+            rq.fakeworkerpipe.setrunqueueexec(self)
+
+        if self.number_tasks <= 0:
+             bb.fatal("Invalid BB_NUMBER_THREADS %s" % self.number_tasks)
+
+    def runqueue_process_waitpid(self, task, status):
+
+        # self.build_stamps[pid] may not exist when use shared work directory.
+        if task in self.build_stamps:
+            self.build_stamps2.remove(self.build_stamps[task])
+            del self.build_stamps[task]
+
+        if status != 0:
+            self.task_fail(task, status)
+        else:
+            self.task_complete(task)
+        return True
+
+    def finish_now(self):
+
+        for worker in [self.rq.worker, self.rq.fakeworker]:
+            if not worker:
+                continue
+            try:
+                worker.stdin.write("<finishnow></finishnow>")
+                worker.stdin.flush()
+            except IOError:
+                # worker must have died?
+                pass
+
+        if len(self.failed_fnids) != 0:
+            self.rq.state = runQueueFailed
+            return
+
+        self.rq.state = runQueueComplete
+        return
+
+    def finish(self):
+        self.rq.state = runQueueCleanUp
+
+        if self.stats.active > 0:
+            bb.event.fire(runQueueExitWait(self.stats.active), self.cfgData)
+            self.rq.read_workers()
+            return self.rq.active_fds()
+
+        if len(self.failed_fnids) != 0:
+            self.rq.state = runQueueFailed
+            return True
+
+        self.rq.state = runQueueComplete
+        return True
+
+    def check_dependencies(self, task, taskdeps, setscene = False):
+        if not self.rq.depvalidate:
+            return False
+
+        taskdata = {}
+        taskdeps.add(task)
+        for dep in taskdeps:
+            if setscene:
+                depid = self.rqdata.runq_setscene[dep]
+            else:
+                depid = dep
+            fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[depid]]
+            pn = self.rqdata.dataCache.pkg_fn[fn]
+            taskname = self.rqdata.runq_task[depid]
+            taskdata[dep] = [pn, taskname, fn]
+        call = self.rq.depvalidate + "(task, taskdata, notneeded, d)"
+        locs = { "task" : task, "taskdata" : taskdata, "notneeded" : self.scenequeue_notneeded, "d" : self.cooker.expanded_data }
+        valid = bb.utils.better_eval(call, locs)
+        return valid
+
+class RunQueueExecuteDummy(RunQueueExecute):
+    def __init__(self, rq):
+        self.rq = rq
+        self.stats = RunQueueStats(0)
+
+    def finish(self):
+        self.rq.state = runQueueComplete
+        return
+
+class RunQueueExecuteTasks(RunQueueExecute):
+    def __init__(self, rq):
+        RunQueueExecute.__init__(self, rq)
+
+        self.stats = RunQueueStats(len(self.rqdata.runq_fnid))
+
+        self.stampcache = {}
+
+        initial_covered = self.rq.scenequeue_covered.copy()
+
+        # Mark initial buildable tasks
+        for task in xrange(self.stats.total):
+            self.runq_running.append(0)
+            self.runq_complete.append(0)
+            if len(self.rqdata.runq_depends[task]) == 0:
+                self.runq_buildable.append(1)
+            else:
+                self.runq_buildable.append(0)
+            if len(self.rqdata.runq_revdeps[task]) > 0 and self.rqdata.runq_revdeps[task].issubset(self.rq.scenequeue_covered) and task not in self.rq.scenequeue_notcovered:
+                self.rq.scenequeue_covered.add(task)
+
+        found = True
+        while found:
+            found = False
+            for task in xrange(self.stats.total):
+                if task in self.rq.scenequeue_covered:
+                    continue
+                logger.debug(1, 'Considering %s (%s): %s' % (task, self.rqdata.get_user_idstring(task), str(self.rqdata.runq_revdeps[task])))
+
+                if len(self.rqdata.runq_revdeps[task]) > 0 and self.rqdata.runq_revdeps[task].issubset(self.rq.scenequeue_covered) and task not in self.rq.scenequeue_notcovered:
+                    found = True
+                    self.rq.scenequeue_covered.add(task)
+
+        logger.debug(1, 'Skip list (pre setsceneverify) %s', sorted(self.rq.scenequeue_covered))
+
+        # Allow the metadata to elect for setscene tasks to run anyway
+        covered_remove = set()
+        if self.rq.setsceneverify:
+            invalidtasks = []
+            for task in xrange(len(self.rqdata.runq_task)):
+                fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[task]]
+                taskname = self.rqdata.runq_task[task]
+                taskdep = self.rqdata.dataCache.task_deps[fn]
+
+                if 'noexec' in taskdep and taskname in taskdep['noexec']:
+                    continue
+                if self.rq.check_stamp_task(task, taskname + "_setscene", cache=self.stampcache):
+                    logger.debug(2, 'Setscene stamp current for task %s(%s)', task, self.rqdata.get_user_idstring(task))
+                    continue
+                if self.rq.check_stamp_task(task, taskname, recurse = True, cache=self.stampcache):
+                    logger.debug(2, 'Normal stamp current for task %s(%s)', task, self.rqdata.get_user_idstring(task))
+                    continue
+                invalidtasks.append(task)
+
+            call = self.rq.setsceneverify + "(covered, tasknames, fnids, fns, d, invalidtasks=invalidtasks)"
+            call2 = self.rq.setsceneverify + "(covered, tasknames, fnids, fns, d)"
+            locs = { "covered" : self.rq.scenequeue_covered, "tasknames" : self.rqdata.runq_task, "fnids" : self.rqdata.runq_fnid, "fns" : self.rqdata.taskData.fn_index, "d" : self.cooker.expanded_data, "invalidtasks" : invalidtasks }
+            # Backwards compatibility with older versions without invalidtasks
+            try:
+                covered_remove = bb.utils.better_eval(call, locs)
+            except TypeError:
+                covered_remove = bb.utils.better_eval(call2, locs)
+
+        def removecoveredtask(task):
+            fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[task]]
+            taskname = self.rqdata.runq_task[task] + '_setscene'
+            bb.build.del_stamp(taskname, self.rqdata.dataCache, fn)
+            self.rq.scenequeue_covered.remove(task)
+
+        toremove = covered_remove
+        for task in toremove:
+            logger.debug(1, 'Not skipping task %s due to setsceneverify', task)
+        while toremove:
+            covered_remove = []
+            for task in toremove:
+                removecoveredtask(task)
+                for deptask in self.rqdata.runq_depends[task]:
+                    if deptask not in self.rq.scenequeue_covered:
+                        continue
+                    if deptask in toremove or deptask in covered_remove or deptask in initial_covered:
+                        continue
+                    logger.debug(1, 'Task %s depends on task %s so not skipping' % (task, deptask))
+                    covered_remove.append(deptask)
+            toremove = covered_remove
+
+        logger.debug(1, 'Full skip list %s', self.rq.scenequeue_covered)
+
+        event.fire(bb.event.StampUpdate(self.rqdata.target_pairs, self.rqdata.dataCache.stamp), self.cfgData)
+
+        schedulers = self.get_schedulers()
+        for scheduler in schedulers:
+            if self.scheduler == scheduler.name:
+                self.sched = scheduler(self, self.rqdata)
+                logger.debug(1, "Using runqueue scheduler '%s'", scheduler.name)
+                break
+        else:
+            bb.fatal("Invalid scheduler '%s'.  Available schedulers: %s" %
+                     (self.scheduler, ", ".join(obj.name for obj in schedulers)))
+
+    def get_schedulers(self):
+        schedulers = set(obj for obj in globals().values()
+                             if type(obj) is type and
+                                issubclass(obj, RunQueueScheduler))
+
+        user_schedulers = self.cfgData.getVar("BB_SCHEDULERS", True)
+        if user_schedulers:
+            for sched in user_schedulers.split():
+                if not "." in sched:
+                    bb.note("Ignoring scheduler '%s' from BB_SCHEDULERS: not an import" % sched)
+                    continue
+
+                modname, name = sched.rsplit(".", 1)
+                try:
+                    module = __import__(modname, fromlist=(name,))
+                except ImportError as exc:
+                    logger.critical("Unable to import scheduler '%s' from '%s': %s" % (name, modname, exc))
+                    raise SystemExit(1)
+                else:
+                    schedulers.add(getattr(module, name))
+        return schedulers
+
+    def setbuildable(self, task):
+        self.runq_buildable[task] = 1
+        self.sched.newbuilable(task)
+
+    def task_completeoutright(self, task):
+        """
+        Mark a task as completed
+        Look at the reverse dependencies and mark any task with
+        completed dependencies as buildable
+        """
+        self.runq_complete[task] = 1
+        for revdep in self.rqdata.runq_revdeps[task]:
+            if self.runq_running[revdep] == 1:
+                continue
+            if self.runq_buildable[revdep] == 1:
+                continue
+            alldeps = 1
+            for dep in self.rqdata.runq_depends[revdep]:
+                if self.runq_complete[dep] != 1:
+                    alldeps = 0
+            if alldeps == 1:
+                self.setbuildable(revdep)
+                fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[revdep]]
+                taskname = self.rqdata.runq_task[revdep]
+                logger.debug(1, "Marking task %s (%s, %s) as buildable", revdep, fn, taskname)
+
+    def task_complete(self, task):
+        self.stats.taskCompleted()
+        bb.event.fire(runQueueTaskCompleted(task, self.stats, self.rq), self.cfgData)
+        self.task_completeoutright(task)
+
+    def task_fail(self, task, exitcode):
+        """
+        Called when a task has failed
+        Updates the state engine with the failure
+        """
+        self.stats.taskFailed()
+        fnid = self.rqdata.runq_fnid[task]
+        self.failed_fnids.append(fnid)
+        bb.event.fire(runQueueTaskFailed(task, self.stats, exitcode, self.rq), self.cfgData)
+        if self.rqdata.taskData.abort:
+            self.rq.state = runQueueCleanUp
+
+    def task_skip(self, task, reason):
+        self.runq_running[task] = 1
+        self.setbuildable(task)
+        bb.event.fire(runQueueTaskSkipped(task, self.stats, self.rq, reason), self.cfgData)
+        self.task_completeoutright(task)
+        self.stats.taskCompleted()
+        self.stats.taskSkipped()
+
+    def execute(self):
+        """
+        Run the tasks in a queue prepared by rqdata.prepare()
+        """
+
+        self.rq.read_workers()
+        
+
+        if self.stats.total == 0:
+            # nothing to do
+            self.rq.state = runQueueCleanUp
+
+        task = self.sched.next()
+        if task is not None:
+            fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[task]]
+            taskname = self.rqdata.runq_task[task]
+
+            if task in self.rq.scenequeue_covered:
+                logger.debug(2, "Setscene covered task %s (%s)", task,
+                                self.rqdata.get_user_idstring(task))
+                self.task_skip(task, "covered")
+                return True
+
+            if self.rq.check_stamp_task(task, taskname, cache=self.stampcache):
+                logger.debug(2, "Stamp current task %s (%s)", task,
+                                self.rqdata.get_user_idstring(task))
+                self.task_skip(task, "existing")
+                return True
+
+            taskdep = self.rqdata.dataCache.task_deps[fn]
+            if 'noexec' in taskdep and taskname in taskdep['noexec']:
+                startevent = runQueueTaskStarted(task, self.stats, self.rq,
+                                                 noexec=True)
+                bb.event.fire(startevent, self.cfgData)
+                self.runq_running[task] = 1
+                self.stats.taskActive()
+                if not self.cooker.configuration.dry_run:
+                    bb.build.make_stamp(taskname, self.rqdata.dataCache, fn)
+                self.task_complete(task)
+                return True
+            else:
+                startevent = runQueueTaskStarted(task, self.stats, self.rq)
+                bb.event.fire(startevent, self.cfgData)
+
+            taskdepdata = self.build_taskdepdata(task)
+
+            taskdep = self.rqdata.dataCache.task_deps[fn]
+            if 'fakeroot' in taskdep and taskname in taskdep['fakeroot'] and not self.cooker.configuration.dry_run:
+                if not self.rq.fakeworker:
+                    try:
+                        self.rq.start_fakeworker(self)
+                    except OSError as exc:
+                        logger.critical("Failed to spawn fakeroot worker to run %s:%s: %s" % (fn, taskname, str(exc)))
+                        self.rq.state = runQueueFailed
+                        return True
+                self.rq.fakeworker.stdin.write("<runtask>" + pickle.dumps((fn, task, taskname, False, self.cooker.collection.get_file_appends(fn), taskdepdata)) + "</runtask>")
+                self.rq.fakeworker.stdin.flush()
+            else:
+                self.rq.worker.stdin.write("<runtask>" + pickle.dumps((fn, task, taskname, False, self.cooker.collection.get_file_appends(fn), taskdepdata)) + "</runtask>")
+                self.rq.worker.stdin.flush()
+
+            self.build_stamps[task] = bb.build.stampfile(taskname, self.rqdata.dataCache, fn)
+            self.build_stamps2.append(self.build_stamps[task]) 
+            self.runq_running[task] = 1
+            self.stats.taskActive()
+            if self.stats.active < self.number_tasks:
+                return True
+
+        if self.stats.active > 0:
+            self.rq.read_workers()
+            return self.rq.active_fds()
+
+        if len(self.failed_fnids) != 0:
+            self.rq.state = runQueueFailed
+            return True
+
+        # Sanity Checks
+        for task in xrange(self.stats.total):
+            if self.runq_buildable[task] == 0:
+                logger.error("Task %s never buildable!", task)
+            if self.runq_running[task] == 0:
+                logger.error("Task %s never ran!", task)
+            if self.runq_complete[task] == 0:
+                logger.error("Task %s never completed!", task)
+        self.rq.state = runQueueComplete
+
+        return True
+
+    def build_taskdepdata(self, task):
+        taskdepdata = {}
+        next = self.rqdata.runq_depends[task]
+        next.add(task)
+        while next:
+            additional = []
+            for revdep in next:
+                fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[revdep]]
+                pn = self.rqdata.dataCache.pkg_fn[fn]
+                taskname = self.rqdata.runq_task[revdep]
+                deps = self.rqdata.runq_depends[revdep]
+                provides = self.rqdata.dataCache.fn_provides[fn]
+                taskdepdata[revdep] = [pn, taskname, fn, deps, provides]
+                for revdep2 in deps:
+                    if revdep2 not in taskdepdata:
+                        additional.append(revdep2)
+            next = additional
+
+        #bb.note("Task %s: " % task + str(taskdepdata).replace("], ", "],\n"))
+        return taskdepdata
+
+class RunQueueExecuteScenequeue(RunQueueExecute):
+    def __init__(self, rq):
+        RunQueueExecute.__init__(self, rq)
+
+        self.scenequeue_covered = set()
+        self.scenequeue_notcovered = set()
+        self.scenequeue_notneeded = set()
+
+        # If we don't have any setscene functions, skip this step
+        if len(self.rqdata.runq_setscene) == 0:
+            rq.scenequeue_covered = set()
+            rq.state = runQueueRunInit
+            return
+
+        self.stats = RunQueueStats(len(self.rqdata.runq_setscene))
+
+        sq_revdeps = []
+        sq_revdeps_new = []
+        sq_revdeps_squash = []
+        self.sq_harddeps = {}
+
+        # We need to construct a dependency graph for the setscene functions. Intermediate
+        # dependencies between the setscene tasks only complicate the code. This code
+        # therefore aims to collapse the huge runqueue dependency tree into a smaller one
+        # only containing the setscene functions.
+
+        for task in xrange(self.stats.total):
+            self.runq_running.append(0)
+            self.runq_complete.append(0)
+            self.runq_buildable.append(0)
+
+        # First process the chains up to the first setscene task.
+        endpoints = {}
+        for task in xrange(len(self.rqdata.runq_fnid)):
+            sq_revdeps.append(copy.copy(self.rqdata.runq_revdeps[task]))
+            sq_revdeps_new.append(set())
+            if (len(self.rqdata.runq_revdeps[task]) == 0) and task not in self.rqdata.runq_setscene:
+                endpoints[task] = set()
+
+        # Secondly process the chains between setscene tasks.
+        for task in self.rqdata.runq_setscene:
+            for dep in self.rqdata.runq_depends[task]:
+                    if dep not in endpoints:
+                        endpoints[dep] = set()
+                    endpoints[dep].add(task)
+
+        def process_endpoints(endpoints):
+            newendpoints = {}
+            for point, task in endpoints.items():
+                tasks = set()
+                if task:
+                    tasks |= task
+                if sq_revdeps_new[point]:
+                    tasks |= sq_revdeps_new[point]
+                sq_revdeps_new[point] = set()
+                if point in self.rqdata.runq_setscene:
+                    sq_revdeps_new[point] = tasks
+                for dep in self.rqdata.runq_depends[point]:
+                    if point in sq_revdeps[dep]:
+                        sq_revdeps[dep].remove(point)
+                    if tasks:
+                        sq_revdeps_new[dep] |= tasks
+                    if (len(sq_revdeps[dep]) == 0 or len(sq_revdeps_new[dep]) != 0) and dep not in self.rqdata.runq_setscene:
+                        newendpoints[dep] = task
+            if len(newendpoints) != 0:
+                process_endpoints(newendpoints)
+
+        process_endpoints(endpoints)
+
+        # Build a list of setscene tasks which are "unskippable"
+        # These are direct endpoints referenced by the build
+        endpoints2 = {}
+        sq_revdeps2 = []
+        sq_revdeps_new2 = []
+        def process_endpoints2(endpoints):
+            newendpoints = {}
+            for point, task in endpoints.items():
+                tasks = set([point])
+                if task:
+                    tasks |= task
+                if sq_revdeps_new2[point]:
+                    tasks |= sq_revdeps_new2[point]
+                sq_revdeps_new2[point] = set()
+                if point in self.rqdata.runq_setscene:
+                    sq_revdeps_new2[point] = tasks
+                for dep in self.rqdata.runq_depends[point]:
+                    if point in sq_revdeps2[dep]:
+                        sq_revdeps2[dep].remove(point)
+                    if tasks:
+                        sq_revdeps_new2[dep] |= tasks
+                    if (len(sq_revdeps2[dep]) == 0 or len(sq_revdeps_new2[dep]) != 0) and dep not in self.rqdata.runq_setscene:
+                        newendpoints[dep] = tasks
+            if len(newendpoints) != 0:
+                process_endpoints2(newendpoints)
+        for task in xrange(len(self.rqdata.runq_fnid)):
+            sq_revdeps2.append(copy.copy(self.rqdata.runq_revdeps[task]))
+            sq_revdeps_new2.append(set())
+            if (len(self.rqdata.runq_revdeps[task]) == 0) and task not in self.rqdata.runq_setscene:
+                endpoints2[task] = set()
+        process_endpoints2(endpoints2)
+        self.unskippable = []
+        for task in self.rqdata.runq_setscene:
+            if sq_revdeps_new2[task]:
+                self.unskippable.append(self.rqdata.runq_setscene.index(task))
+
+        for task in xrange(len(self.rqdata.runq_fnid)):
+            if task in self.rqdata.runq_setscene:
+                deps = set()
+                for dep in sq_revdeps_new[task]:
+                    deps.add(self.rqdata.runq_setscene.index(dep))
+                sq_revdeps_squash.append(deps)
+            elif len(sq_revdeps_new[task]) != 0:
+                bb.msg.fatal("RunQueue", "Something went badly wrong during scenequeue generation, aborting. Please report this problem.")
+
+        # Resolve setscene inter-task dependencies
+        # e.g. do_sometask_setscene[depends] = "targetname:do_someothertask_setscene"
+        # Note that anything explicitly depended upon will have its reverse dependencies removed to avoid circular dependencies
+        for task in self.rqdata.runq_setscene:
+                realid = self.rqdata.taskData.gettask_id(self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[task]], self.rqdata.runq_task[task] + "_setscene", False)
+                idepends = self.rqdata.taskData.tasks_idepends[realid]
+                for (depid, idependtask) in idepends:
+                    if depid not in self.rqdata.taskData.build_targets:
+                        continue
+
+                    depdata = self.rqdata.taskData.build_targets[depid][0]
+                    if depdata is None:
+                         continue
+                    dep = self.rqdata.taskData.fn_index[depdata]
+                    taskid = self.rqdata.get_task_id(self.rqdata.taskData.getfn_id(dep), idependtask.replace("_setscene", ""))
+                    if taskid is None:
+                        bb.msg.fatal("RunQueue", "Task %s_setscene depends upon non-existent task %s:%s" % (self.rqdata.get_user_idstring(task), dep, idependtask))
+
+                    if not self.rqdata.runq_setscene.index(taskid) in self.sq_harddeps:
+                        self.sq_harddeps[self.rqdata.runq_setscene.index(taskid)] = set()
+                    self.sq_harddeps[self.rqdata.runq_setscene.index(taskid)].add(self.rqdata.runq_setscene.index(task))
+
+                    sq_revdeps_squash[self.rqdata.runq_setscene.index(task)].add(self.rqdata.runq_setscene.index(taskid))
+                    # Have to zero this to avoid circular dependencies
+                    sq_revdeps_squash[self.rqdata.runq_setscene.index(taskid)] = set()
+
+        for task in self.sq_harddeps:
+             for dep in self.sq_harddeps[task]:
+                 sq_revdeps_squash[dep].add(task)
+
+        #for task in xrange(len(sq_revdeps_squash)):
+        #    realtask = self.rqdata.runq_setscene[task]
+        #    bb.warn("Task %s: %s_setscene is %s " % (task, self.rqdata.get_user_idstring(realtask) , sq_revdeps_squash[task]))
+
+        self.sq_deps = []
+        self.sq_revdeps = sq_revdeps_squash
+        self.sq_revdeps2 = copy.deepcopy(self.sq_revdeps)
+
+        for task in xrange(len(self.sq_revdeps)):
+            self.sq_deps.append(set())
+        for task in xrange(len(self.sq_revdeps)):
+            for dep in self.sq_revdeps[task]:
+                self.sq_deps[dep].add(task)
+
+        for task in xrange(len(self.sq_revdeps)):
+            if len(self.sq_revdeps[task]) == 0:
+                self.runq_buildable[task] = 1
+
+        self.outrightfail = []
+        if self.rq.hashvalidate:
+            sq_hash = []
+            sq_hashfn = []
+            sq_fn = []
+            sq_taskname = []
+            sq_task = []
+            noexec = []
+            stamppresent = []
+            for task in xrange(len(self.sq_revdeps)):
+                realtask = self.rqdata.runq_setscene[task]
+                fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[realtask]]
+                taskname = self.rqdata.runq_task[realtask]
+                taskdep = self.rqdata.dataCache.task_deps[fn]
+
+                if 'noexec' in taskdep and taskname in taskdep['noexec']:
+                    noexec.append(task)
+                    self.task_skip(task)
+                    bb.build.make_stamp(taskname + "_setscene", self.rqdata.dataCache, fn)
+                    continue
+
+                if self.rq.check_stamp_task(realtask, taskname + "_setscene", cache=self.stampcache):
+                    logger.debug(2, 'Setscene stamp current for task %s(%s)', task, self.rqdata.get_user_idstring(realtask))
+                    stamppresent.append(task)
+                    self.task_skip(task)
+                    continue
+
+                if self.rq.check_stamp_task(realtask, taskname, recurse = True, cache=self.stampcache):
+                    logger.debug(2, 'Normal stamp current for task %s(%s)', task, self.rqdata.get_user_idstring(realtask))
+                    stamppresent.append(task)
+                    self.task_skip(task)
+                    continue
+
+                sq_fn.append(fn)
+                sq_hashfn.append(self.rqdata.dataCache.hashfn[fn])
+                sq_hash.append(self.rqdata.runq_hash[realtask])
+                sq_taskname.append(taskname)
+                sq_task.append(task)
+            call = self.rq.hashvalidate + "(sq_fn, sq_task, sq_hash, sq_hashfn, d)"
+            locs = { "sq_fn" : sq_fn, "sq_task" : sq_taskname, "sq_hash" : sq_hash, "sq_hashfn" : sq_hashfn, "d" : self.cooker.expanded_data }
+            valid = bb.utils.better_eval(call, locs)
+
+            valid_new = stamppresent
+            for v in valid:
+                valid_new.append(sq_task[v])
+
+            for task in xrange(len(self.sq_revdeps)):
+                if task not in valid_new and task not in noexec:
+                    realtask = self.rqdata.runq_setscene[task]
+                    logger.debug(2, 'No package found, so skipping setscene task %s',
+                                 self.rqdata.get_user_idstring(realtask))
+                    self.outrightfail.append(task)
+
+        logger.info('Executing SetScene Tasks')
+
+        self.rq.state = runQueueSceneRun
+
+    def scenequeue_updatecounters(self, task, fail = False):
+        for dep in self.sq_deps[task]:
+            if fail and task in self.sq_harddeps and dep in self.sq_harddeps[task]:
+                realtask = self.rqdata.runq_setscene[task]
+                realdep = self.rqdata.runq_setscene[dep]
+                logger.debug(2, "%s was unavailable and is a hard dependency of %s so skipping" % (self.rqdata.get_user_idstring(realtask), self.rqdata.get_user_idstring(realdep)))
+                self.scenequeue_updatecounters(dep, fail)
+                continue
+            if task not in self.sq_revdeps2[dep]:
+                # May already have been removed by the fail case above
+                continue
+            self.sq_revdeps2[dep].remove(task)
+            if len(self.sq_revdeps2[dep]) == 0:
+                self.runq_buildable[dep] = 1
+
+    def task_completeoutright(self, task):
+        """
+        Mark a task as completed
+        Look at the reverse dependencies and mark any task with
+        completed dependencies as buildable
+        """
+
+        index = self.rqdata.runq_setscene[task]
+        logger.debug(1, 'Found task %s which could be accelerated',
+                        self.rqdata.get_user_idstring(index))
+
+        self.scenequeue_covered.add(task)
+        self.scenequeue_updatecounters(task)
+
+    def task_complete(self, task):
+        self.stats.taskCompleted()
+        bb.event.fire(sceneQueueTaskCompleted(task, self.stats, self.rq), self.cfgData)
+        self.task_completeoutright(task)
+
+    def task_fail(self, task, result):
+        self.stats.taskFailed()
+        bb.event.fire(sceneQueueTaskFailed(task, self.stats, result, self), self.cfgData)
+        self.scenequeue_notcovered.add(task)
+        self.scenequeue_updatecounters(task, True)
+
+    def task_failoutright(self, task):
+        self.runq_running[task] = 1
+        self.runq_buildable[task] = 1
+        self.stats.taskCompleted()
+        self.stats.taskSkipped()
+        index = self.rqdata.runq_setscene[task]
+        self.scenequeue_notcovered.add(task)
+        self.scenequeue_updatecounters(task, True)
+
+    def task_skip(self, task):
+        self.runq_running[task] = 1
+        self.runq_buildable[task] = 1
+        self.task_completeoutright(task)
+        self.stats.taskCompleted()
+        self.stats.taskSkipped()
+
+    def execute(self):
+        """
+        Run the tasks in a queue prepared by prepare_runqueue
+        """
+
+        self.rq.read_workers()
+
+        task = None
+        if self.stats.active < self.number_tasks:
+            # Find the next setscene to run
+            for nexttask in xrange(self.stats.total):
+                if self.runq_buildable[nexttask] == 1 and self.runq_running[nexttask] != 1:
+                    if nexttask in self.unskippable:
+                        logger.debug(2, "Setscene task %s is unskippable" % self.rqdata.get_user_idstring(self.rqdata.runq_setscene[nexttask]))                      
+                    if nexttask not in self.unskippable and len(self.sq_revdeps[nexttask]) > 0 and self.sq_revdeps[nexttask].issubset(self.scenequeue_covered) and self.check_dependencies(nexttask, self.sq_revdeps[nexttask], True):
+                        realtask = self.rqdata.runq_setscene[nexttask]
+                        fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[realtask]]
+                        foundtarget = False
+                        for target in self.rqdata.target_pairs:
+                            if target[0] == fn and target[1] == self.rqdata.runq_task[realtask]:
+                                foundtarget = True
+                                break
+                        if not foundtarget:
+                            logger.debug(2, "Skipping setscene for task %s" % self.rqdata.get_user_idstring(self.rqdata.runq_setscene[nexttask]))
+                            self.task_skip(nexttask)
+                            self.scenequeue_notneeded.add(nexttask)
+                            return True
+                    if nexttask in self.outrightfail:
+                        self.task_failoutright(nexttask)
+                        return True
+                    task = nexttask
+                    break
+        if task is not None:
+            realtask = self.rqdata.runq_setscene[task]
+            fn = self.rqdata.taskData.fn_index[self.rqdata.runq_fnid[realtask]]
+
+            taskname = self.rqdata.runq_task[realtask] + "_setscene"
+            if self.rq.check_stamp_task(realtask, self.rqdata.runq_task[realtask], recurse = True, cache=self.stampcache):
+                logger.debug(2, 'Stamp for underlying task %s(%s) is current, so skipping setscene variant',
+                             task, self.rqdata.get_user_idstring(realtask))
+                self.task_failoutright(task)
+                return True
+
+            if self.cooker.configuration.force:
+                for target in self.rqdata.target_pairs:
+                    if target[0] == fn and target[1] == self.rqdata.runq_task[realtask]:
+                        self.task_failoutright(task)
+                        return True
+
+            if self.rq.check_stamp_task(realtask, taskname, cache=self.stampcache):
+                logger.debug(2, 'Setscene stamp current task %s(%s), so skip it and its dependencies',
+                             task, self.rqdata.get_user_idstring(realtask))
+                self.task_skip(task)
+                return True
+
+            startevent = sceneQueueTaskStarted(task, self.stats, self.rq)
+            bb.event.fire(startevent, self.cfgData)
+
+            taskdep = self.rqdata.dataCache.task_deps[fn]
+            if 'fakeroot' in taskdep and taskname in taskdep['fakeroot']:
+                if not self.rq.fakeworker:
+                    self.rq.start_fakeworker(self)
+                self.rq.fakeworker.stdin.write("<runtask>" + pickle.dumps((fn, realtask, taskname, True, self.cooker.collection.get_file_appends(fn), None)) + "</runtask>")
+                self.rq.fakeworker.stdin.flush()
+            else:
+                self.rq.worker.stdin.write("<runtask>" + pickle.dumps((fn, realtask, taskname, True, self.cooker.collection.get_file_appends(fn), None)) + "</runtask>")
+                self.rq.worker.stdin.flush()
+
+            self.runq_running[task] = 1
+            self.stats.taskActive()
+            if self.stats.active < self.number_tasks:
+                return True
+
+        if self.stats.active > 0:
+            self.rq.read_workers()
+            return self.rq.active_fds()
+
+        #for task in xrange(self.stats.total):
+        #    if self.runq_running[task] != 1:
+        #        buildable = self.runq_buildable[task]
+        #        revdeps = self.sq_revdeps[task]
+        #        bb.warn("Found we didn't run %s %s %s %s" % (task, buildable, str(revdeps), self.rqdata.get_user_idstring(self.rqdata.runq_setscene[task])))
+
+        # Convert scenequeue_covered task numbers into full taskgraph ids
+        oldcovered = self.scenequeue_covered
+        self.rq.scenequeue_covered = set()
+        for task in oldcovered:
+            self.rq.scenequeue_covered.add(self.rqdata.runq_setscene[task])
+        self.rq.scenequeue_notcovered = set()
+        for task in self.scenequeue_notcovered:
+            self.rq.scenequeue_notcovered.add(self.rqdata.runq_setscene[task])
+
+        logger.debug(1, 'We can skip tasks %s', sorted(self.rq.scenequeue_covered))
+
+        self.rq.state = runQueueRunInit
+
+        completeevent = sceneQueueComplete(self.stats, self.rq)
+        bb.event.fire(completeevent, self.cfgData)
+
+        return True
+
+    def runqueue_process_waitpid(self, task, status):
+        task = self.rq.rqdata.runq_setscene.index(task)
+
+        RunQueueExecute.runqueue_process_waitpid(self, task, status)
+
+class TaskFailure(Exception):
+    """
+    Exception raised when a task in a runqueue fails
+    """
+    def __init__(self, x):
+        self.args = x
+
+
+class runQueueExitWait(bb.event.Event):
+    """
+    Event when waiting for task processes to exit
+    """
+
+    def __init__(self, remain):
+        self.remain = remain
+        self.message = "Waiting for %s active tasks to finish" % remain
+        bb.event.Event.__init__(self)
+
+class runQueueEvent(bb.event.Event):
+    """
+    Base runQueue event class
+    """
+    def __init__(self, task, stats, rq):
+        self.taskid = task
+        self.taskstring = rq.rqdata.get_user_idstring(task)
+        self.taskname = rq.rqdata.get_task_name(task)
+        self.taskfile = rq.rqdata.get_task_file(task)
+        self.taskhash = rq.rqdata.get_task_hash(task)
+        self.stats = stats.copy()
+        bb.event.Event.__init__(self)
+
+class sceneQueueEvent(runQueueEvent):
+    """
+    Base sceneQueue event class
+    """
+    def __init__(self, task, stats, rq, noexec=False):
+        runQueueEvent.__init__(self, task, stats, rq)
+        realtask = rq.rqdata.runq_setscene[task]
+        self.taskstring = rq.rqdata.get_user_idstring(realtask, "_setscene")
+        self.taskname = rq.rqdata.get_task_name(realtask) + "_setscene"
+        self.taskfile = rq.rqdata.get_task_file(realtask)
+        self.taskhash = rq.rqdata.get_task_hash(realtask)
+
+class runQueueTaskStarted(runQueueEvent):
+    """
+    Event notifying a task was started
+    """
+    def __init__(self, task, stats, rq, noexec=False):
+        runQueueEvent.__init__(self, task, stats, rq)
+        self.noexec = noexec
+
+class sceneQueueTaskStarted(sceneQueueEvent):
+    """
+    Event notifying a setscene task was started
+    """
+    def __init__(self, task, stats, rq, noexec=False):
+        sceneQueueEvent.__init__(self, task, stats, rq)
+        self.noexec = noexec
+
+class runQueueTaskFailed(runQueueEvent):
+    """
+    Event notifying a task failed
+    """
+    def __init__(self, task, stats, exitcode, rq):
+        runQueueEvent.__init__(self, task, stats, rq)
+        self.exitcode = exitcode
+
+class sceneQueueTaskFailed(sceneQueueEvent):
+    """
+    Event notifying a setscene task failed
+    """
+    def __init__(self, task, stats, exitcode, rq):
+        sceneQueueEvent.__init__(self, task, stats, rq)
+        self.exitcode = exitcode
+
+class sceneQueueComplete(sceneQueueEvent):
+    """
+    Event when all the sceneQueue tasks are complete
+    """
+    def __init__(self, stats, rq):
+        self.stats = stats.copy()
+        bb.event.Event.__init__(self)
+
+class runQueueTaskCompleted(runQueueEvent):
+    """
+    Event notifying a task completed
+    """
+
+class sceneQueueTaskCompleted(sceneQueueEvent):
+    """
+    Event notifying a setscene task completed
+    """
+
+class runQueueTaskSkipped(runQueueEvent):
+    """
+    Event notifying a task was skipped
+    """
+    def __init__(self, task, stats, rq, reason):
+        runQueueEvent.__init__(self, task, stats, rq)
+        self.reason = reason
+
+class runQueuePipe():
+    """
+    Abstraction for a pipe between a worker thread and the server
+    """
+    def __init__(self, pipein, pipeout, d, rq, rqexec):
+        self.input = pipein
+        if pipeout:
+            pipeout.close()
+        bb.utils.nonblockingfd(self.input)
+        self.queue = ""
+        self.d = d
+        self.rq = rq
+        self.rqexec = rqexec
+
+    def setrunqueueexec(self, rqexec):
+        self.rqexec = rqexec
+
+    def read(self):
+        for w in [self.rq.worker, self.rq.fakeworker]:
+            if not w:
+                continue
+            w.poll()
+            if w.returncode is not None and not self.rq.teardown:
+                name = None
+                if self.rq.worker and w.pid == self.rq.worker.pid:
+                    name = "Worker"
+                elif self.rq.fakeworker and w.pid == self.rq.fakeworker.pid:
+                    name = "Fakeroot"
+                bb.error("%s process (%s) exited unexpectedly (%s), shutting down..." % (name, w.pid, str(w.returncode)))
+                self.rq.finish_runqueue(True)
+
+        start = len(self.queue)
+        try:
+            self.queue = self.queue + self.input.read(102400)
+        except (OSError, IOError) as e:
+            if e.errno != errno.EAGAIN:
+                raise
+        end = len(self.queue)
+        found = True
+        while found and len(self.queue):
+            found = False
+            index = self.queue.find("</event>")
+            while index != -1 and self.queue.startswith("<event>"):
+                try:
+                    event = pickle.loads(self.queue[7:index])
+                except ValueError as e:
+                    bb.msg.fatal("RunQueue", "failed load pickle '%s': '%s'" % (e, self.queue[7:index]))
+                bb.event.fire_from_worker(event, self.d)
+                found = True
+                self.queue = self.queue[index+8:]
+                index = self.queue.find("</event>")
+            index = self.queue.find("</exitcode>")
+            while index != -1 and self.queue.startswith("<exitcode>"):
+                try:
+                    task, status = pickle.loads(self.queue[10:index])
+                except ValueError as e:
+                    bb.msg.fatal("RunQueue", "failed load pickle '%s': '%s'" % (e, self.queue[10:index]))
+                self.rqexec.runqueue_process_waitpid(task, status)
+                found = True
+                self.queue = self.queue[index+11:]
+                index = self.queue.find("</exitcode>")
+        return (end > start)
+
+    def close(self):
+        while self.read():
+            continue
+        if len(self.queue) > 0:
+            print("Warning, worker left partial message: %s" % self.queue)
+        self.input.close()
diff --git a/bitbake/lib/bb/server/__init__.py b/bitbake/lib/bb/server/__init__.py
new file mode 100644
index 0000000..da5e480
--- /dev/null
+++ b/bitbake/lib/bb/server/__init__.py
@@ -0,0 +1,96 @@
+#
+# BitBake Base Server Code
+#
+# Copyright (C) 2006 - 2007  Michael 'Mickey' Lauer
+# Copyright (C) 2006 - 2008  Richard Purdie
+# Copyright (C) 2013         Alexandru Damian
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+""" Base code for Bitbake server process
+
+Have a common base for that all Bitbake server classes ensures a consistent
+approach to the interface, and minimize risks associated with code duplication.
+
+"""
+
+"""  BaseImplServer() the base class for all XXServer() implementations.
+
+    These classes contain the actual code that runs the server side, i.e.
+    listens for the commands and executes them. Although these implementations
+    contain all the data of the original bitbake command, i.e the cooker instance,
+    they may well run on a different process or even machine.
+
+"""
+
+class BaseImplServer():
+    def __init__(self):
+        self._idlefuns = {}
+
+    def addcooker(self, cooker):
+        self.cooker = cooker
+
+    def register_idle_function(self, function, data):
+        """Register a function to be called while the server is idle"""
+        assert hasattr(function, '__call__')
+        self._idlefuns[function] = data
+
+
+
+""" BitBakeBaseServerConnection class is the common ancestor to all
+    BitBakeServerConnection classes.
+
+    These classes control the remote server. The only command currently
+    implemented is the terminate() command.
+
+"""
+
+class BitBakeBaseServerConnection():
+    def __init__(self, serverImpl):
+        pass
+
+    def terminate(self):
+        pass
+
+
+""" BitBakeBaseServer class is the common ancestor to all Bitbake servers
+
+    Derive this class in order to implement a BitBakeServer which is the
+    controlling stub for the actual server implementation
+
+"""
+class BitBakeBaseServer(object):
+    def initServer(self):
+        self.serverImpl = None  # we ensure a runtime crash if not overloaded
+        self.connection = None
+        return
+
+    def addcooker(self, cooker):
+        self.cooker = cooker
+        self.serverImpl.addcooker(cooker)
+
+    def getServerIdleCB(self):
+        return self.serverImpl.register_idle_function
+
+    def saveConnectionDetails(self):
+        return
+
+    def detach(self):
+        return
+
+    def establishConnection(self, featureset):
+        raise   "Must redefine the %s.establishConnection()" % self.__class__.__name__
+
+    def endSession(self):
+        self.connection.terminate()
diff --git a/bitbake/lib/bb/server/process.py b/bitbake/lib/bb/server/process.py
new file mode 100644
index 0000000..5fca350
--- /dev/null
+++ b/bitbake/lib/bb/server/process.py
@@ -0,0 +1,264 @@
+#
+# BitBake Process based server.
+#
+# Copyright (C) 2010 Bob Foerster <robert@erafx.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+    This module implements a multiprocessing.Process based server for bitbake.
+"""
+
+import bb
+import bb.event
+import itertools
+import logging
+import multiprocessing
+import os
+import signal
+import sys
+import time
+import select
+from Queue import Empty
+from multiprocessing import Event, Process, util, Queue, Pipe, queues, Manager
+
+from . import BitBakeBaseServer, BitBakeBaseServerConnection, BaseImplServer
+
+logger = logging.getLogger('BitBake')
+
+class ServerCommunicator():
+    def __init__(self, connection, event_handle, server):
+        self.connection = connection
+        self.event_handle = event_handle
+        self.server = server
+
+    def runCommand(self, command):
+        # @todo try/except
+        self.connection.send(command)
+
+        if not self.server.is_alive():
+            raise SystemExit
+
+        while True:
+            # don't let the user ctrl-c while we're waiting for a response
+            try:
+                if self.connection.poll(20):
+                    return self.connection.recv()
+                else:
+                    bb.fatal("Timeout while attempting to communicate with bitbake server")
+            except KeyboardInterrupt:
+                pass
+
+    def getEventHandle(self):
+        return self.event_handle.value
+
+class EventAdapter():
+    """
+    Adapter to wrap our event queue since the caller (bb.event) expects to
+    call a send() method, but our actual queue only has put()
+    """
+    def __init__(self, queue):
+        self.queue = queue
+
+    def send(self, event):
+        try:
+            self.queue.put(event)
+        except Exception as err:
+            print("EventAdapter puked: %s" % str(err))
+
+
+class ProcessServer(Process, BaseImplServer):
+    profile_filename = "profile.log"
+    profile_processed_filename = "profile.log.processed"
+
+    def __init__(self, command_channel, event_queue, featurelist):
+        BaseImplServer.__init__(self)
+        Process.__init__(self)
+        self.command_channel = command_channel
+        self.event_queue = event_queue
+        self.event = EventAdapter(event_queue)
+        self.featurelist = featurelist
+        self.quit = False
+
+        self.quitin, self.quitout = Pipe()
+        self.event_handle = multiprocessing.Value("i")
+
+    def run(self):
+        for event in bb.event.ui_queue:
+            self.event_queue.put(event)
+        self.event_handle.value = bb.event.register_UIHhandler(self, True)
+
+        bb.cooker.server_main(self.cooker, self.main)
+
+    def main(self):
+        # Ignore SIGINT within the server, as all SIGINT handling is done by
+        # the UI and communicated to us
+        self.quitin.close()
+        signal.signal(signal.SIGINT, signal.SIG_IGN)
+        while not self.quit:
+            try:
+                if self.command_channel.poll():
+                    command = self.command_channel.recv()
+                    self.runCommand(command)
+                if self.quitout.poll():
+                    self.quitout.recv()
+                    self.quit = True
+                    try:
+                        self.runCommand(["stateForceShutdown"])
+                    except:
+                        pass
+
+                self.idle_commands(.1, [self.command_channel, self.quitout])
+            except Exception:
+                logger.exception('Running command %s', command)
+
+        self.event_queue.close()
+        bb.event.unregister_UIHhandler(self.event_handle.value)
+        self.command_channel.close()
+        self.cooker.shutdown(True)
+        self.quitout.close()
+
+    def idle_commands(self, delay, fds=None):
+        nextsleep = delay
+        if not fds:
+            fds = []
+
+        for function, data in self._idlefuns.items():
+            try:
+                retval = function(self, data, False)
+                if retval is False:
+                    del self._idlefuns[function]
+                    nextsleep = None
+                elif retval is True:
+                    nextsleep = None
+                elif isinstance(retval, float):
+                    if (retval < nextsleep):
+                        nextsleep = retval
+                elif nextsleep is None:
+                    continue
+                else:
+                    fds = fds + retval
+            except SystemExit:
+                raise
+            except Exception as exc:
+                if not isinstance(exc, bb.BBHandledException):
+                    logger.exception('Running idle function')
+                del self._idlefuns[function]
+                self.quit = True
+
+        if nextsleep is not None:
+            select.select(fds,[],[],nextsleep)
+
+    def runCommand(self, command):
+        """
+        Run a cooker command on the server
+        """
+        self.command_channel.send(self.cooker.command.runCommand(command))
+
+    def stop(self):
+        self.quitin.send("quit")
+        self.quitin.close()
+
+class BitBakeProcessServerConnection(BitBakeBaseServerConnection):
+    def __init__(self, serverImpl, ui_channel, event_queue):
+        self.procserver = serverImpl
+        self.ui_channel = ui_channel
+        self.event_queue = event_queue
+        self.connection = ServerCommunicator(self.ui_channel, self.procserver.event_handle, self.procserver)
+        self.events = self.event_queue
+        self.terminated = False
+
+    def sigterm_terminate(self):
+        bb.error("UI received SIGTERM")
+        self.terminate()
+
+    def terminate(self):
+        if self.terminated:
+            return
+        self.terminated = True
+        def flushevents():
+            while True:
+                try:
+                    event = self.event_queue.get(block=False)
+                except (Empty, IOError):
+                    break
+                if isinstance(event, logging.LogRecord):
+                    logger.handle(event)
+
+        signal.signal(signal.SIGINT, signal.SIG_IGN)
+        self.procserver.stop()
+
+        while self.procserver.is_alive():
+            flushevents()
+            self.procserver.join(0.1)
+
+        self.ui_channel.close()
+        self.event_queue.close()
+        self.event_queue.setexit()
+
+# Wrap Queue to provide API which isn't server implementation specific
+class ProcessEventQueue(multiprocessing.queues.Queue):
+    def __init__(self, maxsize):
+        multiprocessing.queues.Queue.__init__(self, maxsize)
+        self.exit = False
+
+    def setexit(self):
+        self.exit = True
+
+    def waitEvent(self, timeout):
+        if self.exit:
+            sys.exit(1)
+        try:
+            if not self.server.is_alive():
+                self.setexit()
+                return None
+            return self.get(True, timeout)
+        except Empty:
+            return None
+
+    def getEvent(self):
+        try:
+            if not self.server.is_alive():
+                self.setexit()
+                return None
+            return self.get(False)
+        except Empty:
+            return None
+
+
+class BitBakeServer(BitBakeBaseServer):
+    def initServer(self):
+        # establish communication channels.  We use bidirectional pipes for
+        # ui <--> server command/response pairs
+        # and a queue for server -> ui event notifications
+        #
+        self.ui_channel, self.server_channel = Pipe()
+        self.event_queue = ProcessEventQueue(0)
+        self.serverImpl = ProcessServer(self.server_channel, self.event_queue, None)
+        self.event_queue.server = self.serverImpl
+
+    def detach(self):
+        self.serverImpl.start()
+        return
+
+    def establishConnection(self, featureset):
+
+        self.connection = BitBakeProcessServerConnection(self.serverImpl, self.ui_channel, self.event_queue)
+
+        _, error = self.connection.connection.runCommand(["setFeatures", featureset])
+        if error:
+            logger.error("Unable to set the cooker to the correct featureset: %s" % error)
+            raise BaseException(error)
+        signal.signal(signal.SIGTERM, lambda i, s: self.connection.sigterm_terminate())
+        return self.connection
diff --git a/bitbake/lib/bb/server/xmlrpc.py b/bitbake/lib/bb/server/xmlrpc.py
new file mode 100644
index 0000000..b7647c1
--- /dev/null
+++ b/bitbake/lib/bb/server/xmlrpc.py
@@ -0,0 +1,392 @@
+#
+# BitBake XMLRPC Server
+#
+# Copyright (C) 2006 - 2007  Michael 'Mickey' Lauer
+# Copyright (C) 2006 - 2008  Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+    This module implements an xmlrpc server for BitBake.
+
+    Use this by deriving a class from BitBakeXMLRPCServer and then adding
+    methods which you want to "export" via XMLRPC. If the methods have the
+    prefix xmlrpc_, then registering those function will happen automatically,
+    if not, you need to call register_function.
+
+    Use register_idle_function() to add a function which the xmlrpc server
+    calls from within server_forever when no requests are pending. Make sure
+    that those functions are non-blocking or else you will introduce latency
+    in the server's main loop.
+"""
+
+import bb
+import xmlrpclib, sys
+from bb import daemonize
+from bb.ui import uievent
+import hashlib, time
+import socket
+import os, signal
+import threading
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+
+DEBUG = False
+
+from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
+import inspect, select, httplib
+
+from . import BitBakeBaseServer, BitBakeBaseServerConnection, BaseImplServer
+
+class BBTransport(xmlrpclib.Transport):
+    def __init__(self, timeout):
+        self.timeout = timeout
+        self.connection_token = None
+        xmlrpclib.Transport.__init__(self)
+
+    # Modified from default to pass timeout to HTTPConnection
+    def make_connection(self, host):
+        #return an existing connection if possible.  This allows
+        #HTTP/1.1 keep-alive.
+        if self._connection and host == self._connection[0]:
+            return self._connection[1]
+
+        # create a HTTP connection object from a host descriptor
+        chost, self._extra_headers, x509 = self.get_host_info(host)
+        #store the host argument along with the connection object
+        self._connection = host, httplib.HTTPConnection(chost, timeout=self.timeout)
+        return self._connection[1]
+
+    def set_connection_token(self, token):
+        self.connection_token = token
+
+    def send_content(self, h, body):
+        if self.connection_token:
+            h.putheader("Bitbake-token", self.connection_token)
+        xmlrpclib.Transport.send_content(self, h, body)
+
+def _create_server(host, port, timeout = 60):
+    t = BBTransport(timeout)
+    s = xmlrpclib.ServerProxy("http://%s:%d/" % (host, port), transport=t, allow_none=True)
+    return s, t
+
+class BitBakeServerCommands():
+
+    def __init__(self, server):
+        self.server = server
+        self.has_client = False
+
+    def registerEventHandler(self, host, port):
+        """
+        Register a remote UI Event Handler
+        """
+        s, t = _create_server(host, port)
+
+        # we don't allow connections if the cooker is running
+        if (self.cooker.state in [bb.cooker.state.parsing, bb.cooker.state.running]):
+            return None
+
+        self.event_handle = bb.event.register_UIHhandler(s, True)
+        return self.event_handle
+
+    def unregisterEventHandler(self, handlerNum):
+        """
+        Unregister a remote UI Event Handler
+        """
+        return bb.event.unregister_UIHhandler(handlerNum)
+
+    def runCommand(self, command):
+        """
+        Run a cooker command on the server
+        """
+        return self.cooker.command.runCommand(command, self.server.readonly)
+
+    def getEventHandle(self):
+        return self.event_handle
+
+    def terminateServer(self):
+        """
+        Trigger the server to quit
+        """
+        self.server.quit = True
+        print("Server (cooker) exiting")
+        return
+
+    def addClient(self):
+        if self.has_client:
+            return None
+        token = hashlib.md5(str(time.time())).hexdigest()
+        self.server.set_connection_token(token)
+        self.has_client = True
+        return token
+
+    def removeClient(self):
+        if self.has_client:
+            self.server.set_connection_token(None)
+            self.has_client = False
+            if self.server.single_use:
+                self.server.quit = True
+
+# This request handler checks if the request has a "Bitbake-token" header
+# field (this comes from the client side) and compares it with its internal
+# "Bitbake-token" field (this comes from the server). If the two are not
+# equal, it is assumed that a client is trying to connect to the server
+# while another client is connected to the server. In this case, a 503 error
+# ("service unavailable") is returned to the client.
+class BitBakeXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
+    def __init__(self, request, client_address, server):
+        self.server = server
+        SimpleXMLRPCRequestHandler.__init__(self, request, client_address, server)
+
+    def do_POST(self):
+        try:
+            remote_token = self.headers["Bitbake-token"]
+        except:
+            remote_token = None
+        if remote_token != self.server.connection_token and remote_token != "observer":
+            self.report_503()
+        else:
+            if remote_token == "observer":
+                self.server.readonly = True
+            else:
+                self.server.readonly = False
+            SimpleXMLRPCRequestHandler.do_POST(self)
+
+    def report_503(self):
+        self.send_response(503)
+        response = 'No more client allowed'
+        self.send_header("Content-type", "text/plain")
+        self.send_header("Content-length", str(len(response)))
+        self.end_headers()
+        self.wfile.write(response)
+
+
+class XMLRPCProxyServer(BaseImplServer):
+    """ not a real working server, but a stub for a proxy server connection
+
+    """
+    def __init__(self, host, port):
+        self.host = host
+        self.port = port
+
+class XMLRPCServer(SimpleXMLRPCServer, BaseImplServer):
+    # remove this when you're done with debugging
+    # allow_reuse_address = True
+
+    def __init__(self, interface):
+        """
+        Constructor
+        """
+        BaseImplServer.__init__(self)
+        if (interface[1] == 0):     # anonymous port, not getting reused
+            self.single_use = True
+        # Use auto port configuration
+        if (interface[1] == -1):
+            interface = (interface[0], 0)
+        SimpleXMLRPCServer.__init__(self, interface,
+                                    requestHandler=BitBakeXMLRPCRequestHandler,
+                                    logRequests=False, allow_none=True)
+        self.host, self.port = self.socket.getsockname()
+        self.connection_token = None
+        #self.register_introspection_functions()
+        self.commands = BitBakeServerCommands(self)
+        self.autoregister_all_functions(self.commands, "")
+        self.interface = interface
+        self.single_use = False
+
+    def addcooker(self, cooker):
+        BaseImplServer.addcooker(self, cooker)
+        self.commands.cooker = cooker
+
+    def autoregister_all_functions(self, context, prefix):
+        """
+        Convenience method for registering all functions in the scope
+        of this class that start with a common prefix
+        """
+        methodlist = inspect.getmembers(context, inspect.ismethod)
+        for name, method in methodlist:
+            if name.startswith(prefix):
+                self.register_function(method, name[len(prefix):])
+
+
+    def serve_forever(self):
+        # Start the actual XMLRPC server
+        bb.cooker.server_main(self.cooker, self._serve_forever)
+
+    def _serve_forever(self):
+        """
+        Serve Requests. Overloaded to honor a quit command
+        """
+        self.quit = False
+        while not self.quit:
+            fds = [self]
+            nextsleep = 0.1
+            for function, data in self._idlefuns.items():
+                retval = None
+                try:
+                    retval = function(self, data, False)
+                    if retval is False:
+                        del self._idlefuns[function]
+                    elif retval is True:
+                        nextsleep = 0
+                    elif isinstance(retval, float):
+                        if (retval < nextsleep):
+                            nextsleep = retval
+                    else:
+                        fds = fds + retval
+                except SystemExit:
+                    raise
+                except:
+                    import traceback
+                    traceback.print_exc()
+                    if retval == None:
+                        # the function execute failed; delete it
+                        del self._idlefuns[function]
+                    pass
+
+            socktimeout = self.socket.gettimeout() or nextsleep
+            socktimeout = min(socktimeout, nextsleep)
+            # Mirror what BaseServer handle_request would do
+            try:
+                fd_sets = select.select(fds, [], [], socktimeout)
+                if fd_sets[0] and self in fd_sets[0]:
+                    self._handle_request_noblock()
+            except IOError:
+                # we ignore interrupted calls
+                pass
+
+        # Tell idle functions we're exiting
+        for function, data in self._idlefuns.items():
+            try:
+                retval = function(self, data, True)
+            except:
+                pass
+        self.server_close()
+        return
+
+    def set_connection_token(self, token):
+        self.connection_token = token
+
+class BitBakeXMLRPCServerConnection(BitBakeBaseServerConnection):
+    def __init__(self, serverImpl, clientinfo=("localhost", 0), observer_only = False, featureset = None):
+        self.connection, self.transport = _create_server(serverImpl.host, serverImpl.port)
+        self.clientinfo = clientinfo
+        self.serverImpl = serverImpl
+        self.observer_only = observer_only
+        if featureset:
+            self.featureset = featureset
+        else:
+            self.featureset = []
+
+    def connect(self, token = None):
+        if token is None:
+            if self.observer_only:
+                token = "observer"
+            else:
+                token = self.connection.addClient()
+
+        if token is None:
+            return None
+
+        self.transport.set_connection_token(token)
+
+        self.events = uievent.BBUIEventQueue(self.connection, self.clientinfo)
+        for event in bb.event.ui_queue:
+            self.events.queue_event(event)
+
+        _, error = self.connection.runCommand(["setFeatures", self.featureset])
+        if error:
+            # disconnect the client, we can't make the setFeature work
+            self.connection.removeClient()
+            # no need to log it here, the error shall be sent to the client
+            raise BaseException(error)
+
+        return self
+
+    def removeClient(self):
+        if not self.observer_only:
+            self.connection.removeClient()
+
+    def terminate(self):
+        # Don't wait for server indefinitely
+        import socket
+        socket.setdefaulttimeout(2)
+        try:
+            self.events.system_quit()
+        except:
+            pass
+        try:
+            self.connection.removeClient()
+        except:
+            pass
+
+class BitBakeServer(BitBakeBaseServer):
+    def initServer(self, interface = ("localhost", 0)):
+        self.interface = interface
+        self.serverImpl = XMLRPCServer(interface)
+
+    def detach(self):
+        daemonize.createDaemon(self.serverImpl.serve_forever, "bitbake-cookerdaemon.log")
+        del self.cooker
+
+    def establishConnection(self, featureset):
+        self.connection = BitBakeXMLRPCServerConnection(self.serverImpl, self.interface, False, featureset)
+        return self.connection.connect()
+
+    def set_connection_token(self, token):
+        self.connection.transport.set_connection_token(token)
+
+class BitBakeXMLRPCClient(BitBakeBaseServer):
+
+    def __init__(self, observer_only = False, token = None):
+        self.token = token
+
+        self.observer_only = observer_only
+        # if we need extra caches, just tell the server to load them all
+        pass
+
+    def saveConnectionDetails(self, remote):
+        self.remote = remote
+
+    def establishConnection(self, featureset):
+        # The format of "remote" must be "server:port"
+        try:
+            [host, port] = self.remote.split(":")
+            port = int(port)
+        except Exception as e:
+            bb.warn("Failed to read remote definition (%s)" % str(e))
+            raise e
+
+        # We need our IP for the server connection. We get the IP
+        # by trying to connect with the server
+        try:
+            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+            s.connect((host, port))
+            ip = s.getsockname()[0]
+            s.close()
+        except Exception as e:
+            bb.warn("Could not create socket for %s:%s (%s)" % (host, port, str(e)))
+            raise e
+        try:
+            self.serverImpl = XMLRPCProxyServer(host, port)
+            self.connection = BitBakeXMLRPCServerConnection(self.serverImpl, (ip, 0), self.observer_only, featureset)
+            return self.connection.connect(self.token)
+        except Exception as e:
+            bb.warn("Could not connect to server at %s:%s (%s)" % (host, port, str(e)))
+            raise e
+
+    def endSession(self):
+        self.connection.removeClient()
diff --git a/bitbake/lib/bb/shell.py b/bitbake/lib/bb/shell.py
new file mode 100644
index 0000000..1dd8d54
--- /dev/null
+++ b/bitbake/lib/bb/shell.py
@@ -0,0 +1,820 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+##########################################################################
+#
+# Copyright (C) 2005-2006 Michael 'Mickey' Lauer <mickey@Vanille.de>
+# Copyright (C) 2005-2006 Vanille Media
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+##########################################################################
+#
+# Thanks to:
+# * Holger Freyther <zecke@handhelds.org>
+# * Justin Patrin <papercrane@reversefold.com>
+#
+##########################################################################
+
+"""
+BitBake Shell
+
+IDEAS:
+    * list defined tasks per package
+    * list classes
+    * toggle force
+    * command to reparse just one (or more) bbfile(s)
+    * automatic check if reparsing is necessary (inotify?)
+    * frontend for bb file manipulation
+    * more shell-like features:
+        - output control, i.e. pipe output into grep, sort, etc.
+        - job control, i.e. bring running commands into background and foreground
+    * start parsing in background right after startup
+    * ncurses interface
+
+PROBLEMS:
+    * force doesn't always work
+    * readline completion for commands with more than one parameters
+
+"""
+
+##########################################################################
+# Import and setup global variables
+##########################################################################
+
+from __future__ import print_function
+from functools import reduce
+try:
+    set
+except NameError:
+    from sets import Set as set
+import sys, os, readline, socket, httplib, urllib, commands, popen2, shlex, Queue, fnmatch
+from bb import data, parse, build, cache, taskdata, runqueue, providers as Providers
+
+__version__ = "0.5.3.1"
+__credits__ = """BitBake Shell Version %s (C) 2005 Michael 'Mickey' Lauer <mickey@Vanille.de>
+Type 'help' for more information, press CTRL-D to exit.""" % __version__
+
+cmds = {}
+leave_mainloop = False
+last_exception = None
+cooker = None
+parsed = False
+debug = os.environ.get( "BBSHELL_DEBUG", "" )
+
+##########################################################################
+# Class BitBakeShellCommands
+##########################################################################
+
+class BitBakeShellCommands:
+    """This class contains the valid commands for the shell"""
+
+    def __init__( self, shell ):
+        """Register all the commands"""
+        self._shell = shell
+        for attr in BitBakeShellCommands.__dict__:
+            if not attr.startswith( "_" ):
+                if attr.endswith( "_" ):
+                    command = attr[:-1].lower()
+                else:
+                    command = attr[:].lower()
+                method = getattr( BitBakeShellCommands, attr )
+                debugOut( "registering command '%s'" % command )
+                # scan number of arguments
+                usage = getattr( method, "usage", "" )
+                if usage != "<...>":
+                    numArgs = len( usage.split() )
+                else:
+                    numArgs = -1
+                shell.registerCommand( command, method, numArgs, "%s %s" % ( command, usage ), method.__doc__ )
+
+    def _checkParsed( self ):
+        if not parsed:
+            print("SHELL: This command needs to parse bbfiles...")
+            self.parse( None )
+
+    def _findProvider( self, item ):
+        self._checkParsed()
+        # Need to use taskData for this information
+        preferred = data.getVar( "PREFERRED_PROVIDER_%s" % item, cooker.configuration.data, 1 )
+        if not preferred: preferred = item
+        try:
+            lv, lf, pv, pf = Providers.findBestProvider(preferred, cooker.configuration.data, cooker.status)
+        except KeyError:
+            if item in cooker.status.providers:
+                pf = cooker.status.providers[item][0]
+            else:
+                pf = None
+        return pf
+
+    def alias( self, params ):
+        """Register a new name for a command"""
+        new, old = params
+        if not old in cmds:
+            print("ERROR: Command '%s' not known" % old)
+        else:
+            cmds[new] = cmds[old]
+            print("OK")
+    alias.usage = "<alias> <command>"
+
+    def buffer( self, params ):
+        """Dump specified output buffer"""
+        index = params[0]
+        print(self._shell.myout.buffer( int( index ) ))
+    buffer.usage = "<index>"
+
+    def buffers( self, params ):
+        """Show the available output buffers"""
+        commands = self._shell.myout.bufferedCommands()
+        if not commands:
+            print("SHELL: No buffered commands available yet. Start doing something.")
+        else:
+            print("="*35, "Available Output Buffers", "="*27)
+            for index, cmd in enumerate( commands ):
+                print("| %s %s" % ( str( index ).ljust( 3 ), cmd ))
+            print("="*88)
+
+    def build( self, params, cmd = "build" ):
+        """Build a providee"""
+        global last_exception
+        globexpr = params[0]
+        self._checkParsed()
+        names = globfilter( cooker.status.pkg_pn, globexpr )
+        if len( names ) == 0: names = [ globexpr ]
+        print("SHELL: Building %s" % ' '.join( names ))
+
+        td = taskdata.TaskData(cooker.configuration.abort)
+        localdata = data.createCopy(cooker.configuration.data)
+        data.update_data(localdata)
+        data.expandKeys(localdata)
+
+        try:
+            tasks = []
+            for name in names:
+                td.add_provider(localdata, cooker.status, name)
+                providers = td.get_provider(name)
+
+                if len(providers) == 0:
+                    raise Providers.NoProvider
+
+                tasks.append([name, "do_%s" % cmd])
+
+            td.add_unresolved(localdata, cooker.status)
+
+            rq = runqueue.RunQueue(cooker, localdata, cooker.status, td, tasks)
+            rq.prepare_runqueue()
+            rq.execute_runqueue()
+
+        except Providers.NoProvider:
+            print("ERROR: No Provider")
+            last_exception = Providers.NoProvider
+
+        except runqueue.TaskFailure as fnids:
+            last_exception = runqueue.TaskFailure
+
+        except build.FuncFailed as e:
+            print("ERROR: Couldn't build '%s'" % names)
+            last_exception = e
+
+
+    build.usage = "<providee>"
+
+    def clean( self, params ):
+        """Clean a providee"""
+        self.build( params, "clean" )
+    clean.usage = "<providee>"
+
+    def compile( self, params ):
+        """Execute 'compile' on a providee"""
+        self.build( params, "compile" )
+    compile.usage = "<providee>"
+
+    def configure( self, params ):
+        """Execute 'configure' on a providee"""
+        self.build( params, "configure" )
+    configure.usage = "<providee>"
+
+    def install( self, params ):
+        """Execute 'install' on a providee"""
+        self.build( params, "install" )
+    install.usage = "<providee>"
+
+    def edit( self, params ):
+        """Call $EDITOR on a providee"""
+        name = params[0]
+        bbfile = self._findProvider( name )
+        if bbfile is not None:
+            os.system( "%s %s" % ( os.environ.get( "EDITOR", "vi" ), bbfile ) )
+        else:
+            print("ERROR: Nothing provides '%s'" % name)
+    edit.usage = "<providee>"
+
+    def environment( self, params ):
+        """Dump out the outer BitBake environment"""
+        cooker.showEnvironment()
+
+    def exit_( self, params ):
+        """Leave the BitBake Shell"""
+        debugOut( "setting leave_mainloop to true" )
+        global leave_mainloop
+        leave_mainloop = True
+
+    def fetch( self, params ):
+        """Fetch a providee"""
+        self.build( params, "fetch" )
+    fetch.usage = "<providee>"
+
+    def fileBuild( self, params, cmd = "build" ):
+        """Parse and build a .bb file"""
+        global last_exception
+        name = params[0]
+        bf = completeFilePath( name )
+        print("SHELL: Calling '%s' on '%s'" % ( cmd, bf ))
+
+        try:
+            cooker.buildFile(bf, cmd)
+        except parse.ParseError:
+            print("ERROR: Unable to open or parse '%s'" % bf)
+        except build.FuncFailed as e:
+            print("ERROR: Couldn't build '%s'" % name)
+            last_exception = e
+
+    fileBuild.usage = "<bbfile>"
+
+    def fileClean( self, params ):
+        """Clean a .bb file"""
+        self.fileBuild( params, "clean" )
+    fileClean.usage = "<bbfile>"
+
+    def fileEdit( self, params ):
+        """Call $EDITOR on a .bb file"""
+        name = params[0]
+        os.system( "%s %s" % ( os.environ.get( "EDITOR", "vi" ), completeFilePath( name ) ) )
+    fileEdit.usage = "<bbfile>"
+
+    def fileRebuild( self, params ):
+        """Rebuild (clean & build) a .bb file"""
+        self.fileBuild( params, "rebuild" )
+    fileRebuild.usage = "<bbfile>"
+
+    def fileReparse( self, params ):
+        """(re)Parse a bb file"""
+        bbfile = params[0]
+        print("SHELL: Parsing '%s'" % bbfile)
+        parse.update_mtime( bbfile )
+        cooker.parser.reparse(bbfile)
+        if False: #fromCache:
+            print("SHELL: File has not been updated, not reparsing")
+        else:
+            print("SHELL: Parsed")
+    fileReparse.usage = "<bbfile>"
+
+    def abort( self, params ):
+        """Toggle abort task execution flag (see bitbake -k)"""
+        cooker.configuration.abort = not cooker.configuration.abort
+        print("SHELL: Abort Flag is now '%s'" % repr( cooker.configuration.abort ))
+
+    def force( self, params ):
+        """Toggle force task execution flag (see bitbake -f)"""
+        cooker.configuration.force = not cooker.configuration.force
+        print("SHELL: Force Flag is now '%s'" % repr( cooker.configuration.force ))
+
+    def help( self, params ):
+        """Show a comprehensive list of commands and their purpose"""
+        print("="*30, "Available Commands", "="*30)
+        for cmd in sorted(cmds):
+            function, numparams, usage, helptext = cmds[cmd]
+            print("| %s | %s" % (usage.ljust(30), helptext))
+        print("="*78)
+
+    def lastError( self, params ):
+        """Show the reason or log that was produced by the last BitBake event exception"""
+        if last_exception is None:
+            print("SHELL: No Errors yet (Phew)...")
+        else:
+            reason, event = last_exception.args
+            print("SHELL: Reason for the last error: '%s'" % reason)
+            if ':' in reason:
+                msg, filename = reason.split( ':' )
+                filename = filename.strip()
+                print("SHELL: Dumping log file for last error:")
+                try:
+                    print(open( filename ).read())
+                except IOError:
+                    print("ERROR: Couldn't open '%s'" % filename)
+
+    def match( self, params ):
+        """Dump all files or providers matching a glob expression"""
+        what, globexpr = params
+        if what == "files":
+            self._checkParsed()
+            for key in globfilter( cooker.status.pkg_fn, globexpr ): print(key)
+        elif what == "providers":
+            self._checkParsed()
+            for key in globfilter( cooker.status.pkg_pn, globexpr ): print(key)
+        else:
+            print("Usage: match %s" % self.print_.usage)
+    match.usage = "<files|providers> <glob>"
+
+    def new( self, params ):
+        """Create a new .bb file and open the editor"""
+        dirname, filename = params
+        packages = '/'.join( data.getVar( "BBFILES", cooker.configuration.data, 1 ).split('/')[:-2] )
+        fulldirname = "%s/%s" % ( packages, dirname )
+
+        if not os.path.exists( fulldirname ):
+            print("SHELL: Creating '%s'" % fulldirname)
+            os.mkdir( fulldirname )
+        if os.path.exists( fulldirname ) and os.path.isdir( fulldirname ):
+            if os.path.exists( "%s/%s" % ( fulldirname, filename ) ):
+                print("SHELL: ERROR: %s/%s already exists" % ( fulldirname, filename ))
+                return False
+            print("SHELL: Creating '%s/%s'" % ( fulldirname, filename ))
+            newpackage = open( "%s/%s" % ( fulldirname, filename ), "w" )
+            print("""DESCRIPTION = ""
+SECTION = ""
+AUTHOR = ""
+HOMEPAGE = ""
+MAINTAINER = ""
+LICENSE = "GPL"
+PR = "r0"
+
+SRC_URI = ""
+
+#inherit base
+
+#do_configure() {
+#
+#}
+
+#do_compile() {
+#
+#}
+
+#do_stage() {
+#
+#}
+
+#do_install() {
+#
+#}
+""", file=newpackage)
+            newpackage.close()
+            os.system( "%s %s/%s" % ( os.environ.get( "EDITOR" ), fulldirname, filename ) )
+    new.usage = "<directory> <filename>"
+
+    def package( self, params ):
+        """Execute 'package' on a providee"""
+        self.build( params, "package" )
+    package.usage = "<providee>"
+
+    def pasteBin( self, params ):
+        """Send a command + output buffer to the pastebin at http://rafb.net/paste"""
+        index = params[0]
+        contents = self._shell.myout.buffer( int( index ) )
+        sendToPastebin( "output of " + params[0], contents )
+    pasteBin.usage = "<index>"
+
+    def pasteLog( self, params ):
+        """Send the last event exception error log (if there is one) to http://rafb.net/paste"""
+        if last_exception is None:
+            print("SHELL: No Errors yet (Phew)...")
+        else:
+            reason, event = last_exception.args
+            print("SHELL: Reason for the last error: '%s'" % reason)
+            if ':' in reason:
+                msg, filename = reason.split( ':' )
+                filename = filename.strip()
+                print("SHELL: Pasting log file to pastebin...")
+
+                file = open( filename ).read()
+                sendToPastebin( "contents of " + filename, file )
+
+    def patch( self, params ):
+        """Execute 'patch' command on a providee"""
+        self.build( params, "patch" )
+    patch.usage = "<providee>"
+
+    def parse( self, params ):
+        """(Re-)parse .bb files and calculate the dependency graph"""
+        cooker.status = cache.CacheData(cooker.caches_array)
+        ignore = data.getVar("ASSUME_PROVIDED", cooker.configuration.data, 1) or ""
+        cooker.status.ignored_dependencies = set( ignore.split() )
+        cooker.handleCollections( data.getVar("BBFILE_COLLECTIONS", cooker.configuration.data, 1) )
+
+        (filelist, masked) = cooker.collect_bbfiles()
+        cooker.parse_bbfiles(filelist, masked, cooker.myProgressCallback)
+        cooker.buildDepgraph()
+        global parsed
+        parsed = True
+        print()
+
+    def reparse( self, params ):
+        """(re)Parse a providee's bb file"""
+        bbfile = self._findProvider( params[0] )
+        if bbfile is not None:
+            print("SHELL: Found bbfile '%s' for '%s'" % ( bbfile, params[0] ))
+            self.fileReparse( [ bbfile ] )
+        else:
+            print("ERROR: Nothing provides '%s'" % params[0])
+    reparse.usage = "<providee>"
+
+    def getvar( self, params ):
+        """Dump the contents of an outer BitBake environment variable"""
+        var = params[0]
+        value = data.getVar( var, cooker.configuration.data, 1 )
+        print(value)
+    getvar.usage = "<variable>"
+
+    def peek( self, params ):
+        """Dump contents of variable defined in providee's metadata"""
+        name, var = params
+        bbfile = self._findProvider( name )
+        if bbfile is not None:
+            the_data = cache.Cache.loadDataFull(bbfile, cooker.configuration.data)
+            value = the_data.getVar( var, 1 )
+            print(value)
+        else:
+            print("ERROR: Nothing provides '%s'" % name)
+    peek.usage = "<providee> <variable>"
+
+    def poke( self, params ):
+        """Set contents of variable defined in providee's metadata"""
+        name, var, value = params
+        bbfile = self._findProvider( name )
+        if bbfile is not None:
+            print("ERROR: Sorry, this functionality is currently broken")
+            #d = cooker.pkgdata[bbfile]
+            #data.setVar( var, value, d )
+
+            # mark the change semi persistant
+            #cooker.pkgdata.setDirty(bbfile, d)
+            #print "OK"
+        else:
+            print("ERROR: Nothing provides '%s'" % name)
+    poke.usage = "<providee> <variable> <value>"
+
+    def print_( self, params ):
+        """Dump all files or providers"""
+        what = params[0]
+        if what == "files":
+            self._checkParsed()
+            for key in cooker.status.pkg_fn: print(key)
+        elif what == "providers":
+            self._checkParsed()
+            for key in cooker.status.providers: print(key)
+        else:
+            print("Usage: print %s" % self.print_.usage)
+    print_.usage = "<files|providers>"
+
+    def python( self, params ):
+        """Enter the expert mode - an interactive BitBake Python Interpreter"""
+        sys.ps1 = "EXPERT BB>>> "
+        sys.ps2 = "EXPERT BB... "
+        import code
+        interpreter = code.InteractiveConsole( dict( globals() ) )
+        interpreter.interact( "SHELL: Expert Mode - BitBake Python %s\nType 'help' for more information, press CTRL-D to switch back to BBSHELL." % sys.version )
+
+    def showdata( self, params ):
+        """Execute 'showdata' on a providee"""
+        cooker.showEnvironment(None, params)
+    showdata.usage = "<providee>"
+
+    def setVar( self, params ):
+        """Set an outer BitBake environment variable"""
+        var, value = params
+        data.setVar( var, value, cooker.configuration.data )
+        print("OK")
+    setVar.usage = "<variable> <value>"
+
+    def rebuild( self, params ):
+        """Clean and rebuild a .bb file or a providee"""
+        self.build( params, "clean" )
+        self.build( params, "build" )
+    rebuild.usage = "<providee>"
+
+    def shell( self, params ):
+        """Execute a shell command and dump the output"""
+        if params != "":
+            print(commands.getoutput( " ".join( params ) ))
+    shell.usage = "<...>"
+
+    def stage( self, params ):
+        """Execute 'stage' on a providee"""
+        self.build( params, "populate_staging" )
+    stage.usage = "<providee>"
+
+    def status( self, params ):
+        """<just for testing>"""
+        print("-" * 78)
+        print("building list = '%s'" % cooker.building_list)
+        print("build path = '%s'" % cooker.build_path)
+        print("consider_msgs_cache = '%s'" % cooker.consider_msgs_cache)
+        print("build stats = '%s'" % cooker.stats)
+        if last_exception is not None: print("last_exception = '%s'" % repr( last_exception.args ))
+        print("memory output contents = '%s'" % self._shell.myout._buffer)
+
+    def test( self, params ):
+        """<just for testing>"""
+        print("testCommand called with '%s'" % params)
+
+    def unpack( self, params ):
+        """Execute 'unpack' on a providee"""
+        self.build( params, "unpack" )
+    unpack.usage = "<providee>"
+
+    def which( self, params ):
+        """Computes the providers for a given providee"""
+        # Need to use taskData for this information
+        item = params[0]
+
+        self._checkParsed()
+
+        preferred = data.getVar( "PREFERRED_PROVIDER_%s" % item, cooker.configuration.data, 1 )
+        if not preferred: preferred = item
+
+        try:
+            lv, lf, pv, pf = Providers.findBestProvider(preferred, cooker.configuration.data, cooker.status)
+        except KeyError:
+            lv, lf, pv, pf = (None,)*4
+
+        try:
+            providers = cooker.status.providers[item]
+        except KeyError:
+            print("SHELL: ERROR: Nothing provides", preferred)
+        else:
+            for provider in providers:
+                if provider == pf: provider = " (***) %s" % provider
+                else:              provider = "       %s" % provider
+                print(provider)
+    which.usage = "<providee>"
+
+##########################################################################
+# Common helper functions
+##########################################################################
+
+def completeFilePath( bbfile ):
+    """Get the complete bbfile path"""
+    if not cooker.status: return bbfile
+    if not cooker.status.pkg_fn: return bbfile
+    for key in cooker.status.pkg_fn:
+        if key.endswith( bbfile ):
+            return key
+    return bbfile
+
+def sendToPastebin( desc, content ):
+    """Send content to http://oe.pastebin.com"""
+    mydata = {}
+    mydata["lang"] = "Plain Text"
+    mydata["desc"] = desc
+    mydata["cvt_tabs"] = "No"
+    mydata["nick"] = "%s@%s" % ( os.environ.get( "USER", "unknown" ), socket.gethostname() or "unknown" )
+    mydata["text"] = content
+    params = urllib.urlencode( mydata )
+    headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
+
+    host = "rafb.net"
+    conn = httplib.HTTPConnection( "%s:80" % host )
+    conn.request("POST", "/paste/paste.php", params, headers )
+
+    response = conn.getresponse()
+    conn.close()
+
+    if response.status == 302:
+        location = response.getheader( "location" ) or "unknown"
+        print("SHELL: Pasted to http://%s%s" % ( host, location ))
+    else:
+        print("ERROR: %s %s" % ( response.status, response.reason ))
+
+def completer( text, state ):
+    """Return a possible readline completion"""
+    debugOut( "completer called with text='%s', state='%d'" % ( text, state ) )
+
+    if state == 0:
+        line = readline.get_line_buffer()
+        if " " in line:
+            line = line.split()
+            # we are in second (or more) argument
+            if line[0] in cmds and hasattr( cmds[line[0]][0], "usage" ): # known command and usage
+                u = getattr( cmds[line[0]][0], "usage" ).split()[0]
+                if u == "<variable>":
+                    allmatches = cooker.configuration.data.keys()
+                elif u == "<bbfile>":
+                    if cooker.status.pkg_fn is None: allmatches = [ "(No Matches Available. Parsed yet?)" ]
+                    else: allmatches = [ x.split("/")[-1] for x in cooker.status.pkg_fn ]
+                elif u == "<providee>":
+                    if cooker.status.pkg_fn is None: allmatches = [ "(No Matches Available. Parsed yet?)" ]
+                    else: allmatches = cooker.status.providers.iterkeys()
+                else: allmatches = [ "(No tab completion available for this command)" ]
+            else: allmatches = [ "(No tab completion available for this command)" ]
+        else:
+            # we are in first argument
+            allmatches = cmds.iterkeys()
+
+        completer.matches = [ x for x in allmatches if x[:len(text)] == text ]
+        #print "completer.matches = '%s'" % completer.matches
+    if len( completer.matches ) > state:
+        return completer.matches[state]
+    else:
+        return None
+
+def debugOut( text ):
+    if debug:
+        sys.stderr.write( "( %s )\n" % text )
+
+def columnize( alist, width = 80 ):
+    """
+    A word-wrap function that preserves existing line breaks
+    and most spaces in the text. Expects that existing line
+    breaks are posix newlines (\n).
+    """
+    return reduce(lambda line, word, width=width: '%s%s%s' %
+                  (line,
+                   ' \n'[(len(line[line.rfind('\n')+1:])
+                         + len(word.split('\n', 1)[0]
+                              ) >= width)],
+                   word),
+                  alist
+                 )
+
+def globfilter( names, pattern ):
+    return fnmatch.filter( names, pattern )
+
+##########################################################################
+# Class MemoryOutput
+##########################################################################
+
+class MemoryOutput:
+    """File-like output class buffering the output of the last 10 commands"""
+    def __init__( self, delegate ):
+        self.delegate = delegate
+        self._buffer = []
+        self.text = []
+        self._command = None
+
+    def startCommand( self, command ):
+        self._command = command
+        self.text = []
+    def endCommand( self ):
+        if self._command is not None:
+            if len( self._buffer ) == 10: del self._buffer[0]
+            self._buffer.append( ( self._command, self.text ) )
+    def removeLast( self ):
+        if self._buffer:
+            del self._buffer[ len( self._buffer ) - 1 ]
+        self.text = []
+        self._command = None
+    def lastBuffer( self ):
+        if self._buffer:
+            return self._buffer[ len( self._buffer ) -1 ][1]
+    def bufferedCommands( self ):
+        return [ cmd for cmd, output in self._buffer ]
+    def buffer( self, i ):
+        if i < len( self._buffer ):
+            return "BB>> %s\n%s" % ( self._buffer[i][0], "".join( self._buffer[i][1] ) )
+        else: return "ERROR: Invalid buffer number. Buffer needs to be in (0, %d)" % ( len( self._buffer ) - 1 )
+    def write( self, text ):
+        if self._command is not None and text != "BB>> ": self.text.append( text )
+        if self.delegate is not None: self.delegate.write( text )
+    def flush( self ):
+        return self.delegate.flush()
+    def fileno( self ):
+        return self.delegate.fileno()
+    def isatty( self ):
+        return self.delegate.isatty()
+
+##########################################################################
+# Class BitBakeShell
+##########################################################################
+
+class BitBakeShell:
+
+    def __init__( self ):
+        """Register commands and set up readline"""
+        self.commandQ = Queue.Queue()
+        self.commands = BitBakeShellCommands( self )
+        self.myout = MemoryOutput( sys.stdout )
+        self.historyfilename = os.path.expanduser( "~/.bbsh_history" )
+        self.startupfilename = os.path.expanduser( "~/.bbsh_startup" )
+
+        readline.set_completer( completer )
+        readline.set_completer_delims( " " )
+        readline.parse_and_bind("tab: complete")
+
+        try:
+            readline.read_history_file( self.historyfilename )
+        except IOError:
+            pass  # It doesn't exist yet.
+
+        print(__credits__)
+
+    def cleanup( self ):
+        """Write readline history and clean up resources"""
+        debugOut( "writing command history" )
+        try:
+            readline.write_history_file( self.historyfilename )
+        except:
+            print("SHELL: Unable to save command history")
+
+    def registerCommand( self, command, function, numparams = 0, usage = "", helptext = "" ):
+        """Register a command"""
+        if usage == "": usage = command
+        if helptext == "": helptext = function.__doc__ or "<not yet documented>"
+        cmds[command] = ( function, numparams, usage, helptext )
+
+    def processCommand( self, command, params ):
+        """Process a command. Check number of params and print a usage string, if appropriate"""
+        debugOut( "processing command '%s'..." % command )
+        try:
+            function, numparams, usage, helptext = cmds[command]
+        except KeyError:
+            print("SHELL: ERROR: '%s' command is not a valid command." % command)
+            self.myout.removeLast()
+        else:
+            if (numparams != -1) and (not len( params ) == numparams):
+                print("Usage: '%s'" % usage)
+                return
+
+            result = function( self.commands, params )
+            debugOut( "result was '%s'" % result )
+
+    def processStartupFile( self ):
+        """Read and execute all commands found in $HOME/.bbsh_startup"""
+        if os.path.exists( self.startupfilename ):
+            startupfile = open( self.startupfilename, "r" )
+            for cmdline in startupfile:
+                debugOut( "processing startup line '%s'" % cmdline )
+                if not cmdline:
+                    continue
+                if "|" in cmdline:
+                    print("ERROR: '|' in startup file is not allowed. Ignoring line")
+                    continue
+                self.commandQ.put( cmdline.strip() )
+
+    def main( self ):
+        """The main command loop"""
+        while not leave_mainloop:
+            try:
+                if self.commandQ.empty():
+                    sys.stdout = self.myout.delegate
+                    cmdline = raw_input( "BB>> " )
+                    sys.stdout = self.myout
+                else:
+                    cmdline = self.commandQ.get()
+                if cmdline:
+                    allCommands = cmdline.split( ';' )
+                    for command in allCommands:
+                        pipecmd = None
+                        #
+                        # special case for expert mode
+                        if command == 'python':
+                            sys.stdout = self.myout.delegate
+                            self.processCommand( command, "" )
+                            sys.stdout = self.myout
+                        else:
+                            self.myout.startCommand( command )
+                            if '|' in command: # disable output
+                                command, pipecmd = command.split( '|' )
+                                delegate = self.myout.delegate
+                                self.myout.delegate = None
+                            tokens = shlex.split( command, True )
+                            self.processCommand( tokens[0], tokens[1:] or "" )
+                            self.myout.endCommand()
+                            if pipecmd is not None: # restore output
+                                self.myout.delegate = delegate
+
+                                pipe = popen2.Popen4( pipecmd )
+                                pipe.tochild.write( "\n".join( self.myout.lastBuffer() ) )
+                                pipe.tochild.close()
+                                sys.stdout.write( pipe.fromchild.read() )
+                        #
+            except EOFError:
+                print()
+                return
+            except KeyboardInterrupt:
+                print()
+
+##########################################################################
+# Start function - called from the BitBake command line utility
+##########################################################################
+
+def start( aCooker ):
+    global cooker
+    cooker = aCooker
+    bbshell = BitBakeShell()
+    bbshell.processStartupFile()
+    bbshell.main()
+    bbshell.cleanup()
+
+if __name__ == "__main__":
+    print("SHELL: Sorry, this program should only be called by BitBake.")
diff --git a/bitbake/lib/bb/siggen.py b/bitbake/lib/bb/siggen.py
new file mode 100644
index 0000000..2985272
--- /dev/null
+++ b/bitbake/lib/bb/siggen.py
@@ -0,0 +1,526 @@
+import hashlib
+import logging
+import os
+import re
+import tempfile
+import bb.data
+
+logger = logging.getLogger('BitBake.SigGen')
+
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+    logger.info('Importing cPickle failed.  Falling back to a very slow implementation.')
+
+def init(d):
+    siggens = [obj for obj in globals().itervalues()
+                      if type(obj) is type and issubclass(obj, SignatureGenerator)]
+
+    desired = d.getVar("BB_SIGNATURE_HANDLER", True) or "noop"
+    for sg in siggens:
+        if desired == sg.name:
+            return sg(d)
+            break
+    else:
+        logger.error("Invalid signature generator '%s', using default 'noop'\n"
+                     "Available generators: %s", desired,
+                     ', '.join(obj.name for obj in siggens))
+        return SignatureGenerator(d)
+
+class SignatureGenerator(object):
+    """
+    """
+    name = "noop"
+
+    def __init__(self, data):
+        self.taskhash = {}
+        self.runtaskdeps = {}
+        self.file_checksum_values = {}
+
+    def finalise(self, fn, d, varient):
+        return
+
+    def get_taskhash(self, fn, task, deps, dataCache):
+        return "0"
+
+    def set_taskdata(self, hashes, deps, checksum):
+        return
+
+    def stampfile(self, stampbase, file_name, taskname, extrainfo):
+        return ("%s.%s.%s" % (stampbase, taskname, extrainfo)).rstrip('.')
+
+    def stampcleanmask(self, stampbase, file_name, taskname, extrainfo):
+        return ("%s.%s.%s" % (stampbase, taskname, extrainfo)).rstrip('.')
+
+    def dump_sigtask(self, fn, task, stampbase, runtime):
+        return
+
+    def invalidate_task(self, task, d, fn):
+        bb.build.del_stamp(task, d, fn)
+
+    def dump_sigs(self, dataCache, options):
+        return
+
+    def get_taskdata(self):
+       return (self.runtaskdeps, self.taskhash, self.file_checksum_values)
+
+    def set_taskdata(self, data):
+        self.runtaskdeps, self.taskhash, self.file_checksum_values = data
+
+
+class SignatureGeneratorBasic(SignatureGenerator):
+    """
+    """
+    name = "basic"
+
+    def __init__(self, data):
+        self.basehash = {}
+        self.taskhash = {}
+        self.taskdeps = {}
+        self.runtaskdeps = {}
+        self.file_checksum_values = {}
+        self.gendeps = {}
+        self.lookupcache = {}
+        self.pkgnameextract = re.compile("(?P<fn>.*)\..*")
+        self.basewhitelist = set((data.getVar("BB_HASHBASE_WHITELIST", True) or "").split())
+        self.taskwhitelist = None
+        self.init_rundepcheck(data)
+
+    def init_rundepcheck(self, data):
+        self.taskwhitelist = data.getVar("BB_HASHTASK_WHITELIST", True) or None
+        if self.taskwhitelist:
+            self.twl = re.compile(self.taskwhitelist)
+        else:
+            self.twl = None
+
+    def _build_data(self, fn, d):
+
+        tasklist, gendeps, lookupcache = bb.data.generate_dependencies(d)
+
+        taskdeps = {}
+        basehash = {}
+
+        for task in tasklist:
+            data = lookupcache[task]
+
+            if data is None:
+                bb.error("Task %s from %s seems to be empty?!" % (task, fn))
+                data = ''
+
+            gendeps[task] -= self.basewhitelist
+            newdeps = gendeps[task]
+            seen = set()
+            while newdeps:
+                nextdeps = newdeps
+                seen |= nextdeps
+                newdeps = set()
+                for dep in nextdeps:
+                    if dep in self.basewhitelist:
+                        continue
+                    gendeps[dep] -= self.basewhitelist
+                    newdeps |= gendeps[dep]
+                newdeps -= seen
+
+            alldeps = sorted(seen)
+            for dep in alldeps:
+                data = data + dep
+                var = lookupcache[dep]
+                if var is not None:
+                    data = data + str(var)
+            self.basehash[fn + "." + task] = hashlib.md5(data).hexdigest()
+            taskdeps[task] = alldeps
+
+        self.taskdeps[fn] = taskdeps
+        self.gendeps[fn] = gendeps
+        self.lookupcache[fn] = lookupcache
+
+        return taskdeps
+
+    def finalise(self, fn, d, variant):
+
+        if variant:
+            fn = "virtual:" + variant + ":" + fn
+
+        try:
+            taskdeps = self._build_data(fn, d)
+        except:
+            bb.note("Error during finalise of %s" % fn)
+            raise
+
+        #Slow but can be useful for debugging mismatched basehashes
+        #for task in self.taskdeps[fn]:
+        #    self.dump_sigtask(fn, task, d.getVar("STAMP", True), False)
+
+        for task in taskdeps:
+            d.setVar("BB_BASEHASH_task-%s" % task, self.basehash[fn + "." + task])
+
+    def rundep_check(self, fn, recipename, task, dep, depname, dataCache):
+        # Return True if we should keep the dependency, False to drop it
+        # We only manipulate the dependencies for packages not in the whitelist
+        if self.twl and not self.twl.search(recipename):
+            # then process the actual dependencies
+            if self.twl.search(depname):
+                return False
+        return True
+
+    def read_taint(self, fn, task, stampbase):
+        taint = None
+        try:
+            with open(stampbase + '.' + task + '.taint', 'r') as taintf:
+                taint = taintf.read()
+        except IOError:
+            pass
+        return taint
+
+    def get_taskhash(self, fn, task, deps, dataCache):
+        k = fn + "." + task
+        data = dataCache.basetaskhash[k]
+        self.runtaskdeps[k] = []
+        self.file_checksum_values[k] = {}
+        recipename = dataCache.pkg_fn[fn]
+        for dep in sorted(deps, key=clean_basepath):
+            depname = dataCache.pkg_fn[self.pkgnameextract.search(dep).group('fn')]
+            if not self.rundep_check(fn, recipename, task, dep, depname, dataCache):
+                continue
+            if dep not in self.taskhash:
+                bb.fatal("%s is not in taskhash, caller isn't calling in dependency order?", dep)
+            data = data + self.taskhash[dep]
+            self.runtaskdeps[k].append(dep)
+
+        if task in dataCache.file_checksums[fn]:
+            checksums = bb.fetch2.get_file_checksums(dataCache.file_checksums[fn][task], recipename)
+            for (f,cs) in checksums:
+                self.file_checksum_values[k][f] = cs
+                if cs:
+                    data = data + cs
+
+        taskdep = dataCache.task_deps[fn]
+        if 'nostamp' in taskdep and task in taskdep['nostamp']:
+            # Nostamp tasks need an implicit taint so that they force any dependent tasks to run
+            import uuid
+            data = data + str(uuid.uuid4())
+
+        taint = self.read_taint(fn, task, dataCache.stamp[fn])
+        if taint:
+            data = data + taint
+            logger.warn("%s is tainted from a forced run" % k)
+
+        h = hashlib.md5(data).hexdigest()
+        self.taskhash[k] = h
+        #d.setVar("BB_TASKHASH_task-%s" % task, taskhash[task])
+        return h
+
+    def dump_sigtask(self, fn, task, stampbase, runtime):
+        k = fn + "." + task
+        if runtime == "customfile":
+            sigfile = stampbase
+        elif runtime and k in self.taskhash:
+            sigfile = stampbase + "." + task + ".sigdata" + "." + self.taskhash[k]
+        else:
+            sigfile = stampbase + "." + task + ".sigbasedata" + "." + self.basehash[k]
+
+        bb.utils.mkdirhier(os.path.dirname(sigfile))
+
+        data = {}
+        data['basewhitelist'] = self.basewhitelist
+        data['taskwhitelist'] = self.taskwhitelist
+        data['taskdeps'] = self.taskdeps[fn][task]
+        data['basehash'] = self.basehash[k]
+        data['gendeps'] = {}
+        data['varvals'] = {}
+        data['varvals'][task] = self.lookupcache[fn][task]
+        for dep in self.taskdeps[fn][task]:
+            if dep in self.basewhitelist:
+                continue
+            data['gendeps'][dep] = self.gendeps[fn][dep]
+            data['varvals'][dep] = self.lookupcache[fn][dep]
+
+        if runtime and k in self.taskhash:
+            data['runtaskdeps'] = self.runtaskdeps[k]
+            data['file_checksum_values'] = [(os.path.basename(f), cs) for f,cs in self.file_checksum_values[k].items()]
+            data['runtaskhashes'] = {}
+            for dep in data['runtaskdeps']:
+                data['runtaskhashes'][dep] = self.taskhash[dep]
+
+        taint = self.read_taint(fn, task, stampbase)
+        if taint:
+            data['taint'] = taint
+
+        fd, tmpfile = tempfile.mkstemp(dir=os.path.dirname(sigfile), prefix="sigtask.")
+        try:
+            with os.fdopen(fd, "wb") as stream:
+                p = pickle.dump(data, stream, -1)
+                stream.flush()
+            os.chmod(tmpfile, 0664)
+            os.rename(tmpfile, sigfile)
+        except (OSError, IOError) as err:
+            try:
+                os.unlink(tmpfile)
+            except OSError:
+                pass
+            raise err
+
+    def dump_sigs(self, dataCache, options):
+        for fn in self.taskdeps:
+            for task in self.taskdeps[fn]:
+                k = fn + "." + task
+                if k not in self.taskhash:
+                    continue
+                if dataCache.basetaskhash[k] != self.basehash[k]:
+                    bb.error("Bitbake's cached basehash does not match the one we just generated (%s)!" % k)
+                    bb.error("The mismatched hashes were %s and %s" % (dataCache.basetaskhash[k], self.basehash[k]))
+                self.dump_sigtask(fn, task, dataCache.stamp[fn], True)
+
+class SignatureGeneratorBasicHash(SignatureGeneratorBasic):
+    name = "basichash"
+
+    def stampfile(self, stampbase, fn, taskname, extrainfo, clean=False):
+        if taskname != "do_setscene" and taskname.endswith("_setscene"):
+            k = fn + "." + taskname[:-9]
+        else:
+            k = fn + "." + taskname
+        if clean:
+            h = "*"
+        elif k in self.taskhash:
+            h = self.taskhash[k]
+        else:
+            # If k is not in basehash, then error
+            h = self.basehash[k]
+        return ("%s.%s.%s.%s" % (stampbase, taskname, h, extrainfo)).rstrip('.')
+
+    def stampcleanmask(self, stampbase, fn, taskname, extrainfo):
+        return self.stampfile(stampbase, fn, taskname, extrainfo, clean=True)
+        
+    def invalidate_task(self, task, d, fn):
+        bb.note("Tainting hash to force rebuild of task %s, %s" % (fn, task))
+        bb.build.write_taint(task, d, fn)
+
+def dump_this_task(outfile, d):
+    import bb.parse
+    fn = d.getVar("BB_FILENAME", True)
+    task = "do_" + d.getVar("BB_CURRENTTASK", True)
+    bb.parse.siggen.dump_sigtask(fn, task, outfile, "customfile")
+
+def clean_basepath(a):
+    b = a.rsplit("/", 2)[1] + a.rsplit("/", 2)[2]
+    if a.startswith("virtual:"):
+        b = b + ":" + a.rsplit(":", 1)[0]
+    return b
+
+def clean_basepaths(a):
+    b = {}
+    for x in a:
+        b[clean_basepath(x)] = a[x]
+    return b
+
+def clean_basepaths_list(a):
+    b = []
+    for x in a:
+        b.append(clean_basepath(x))
+    return b
+
+def compare_sigfiles(a, b, recursecb = None):
+    output = []
+
+    p1 = pickle.Unpickler(open(a, "rb"))
+    a_data = p1.load()
+    p2 = pickle.Unpickler(open(b, "rb"))
+    b_data = p2.load()
+
+    def dict_diff(a, b, whitelist=set()):
+        sa = set(a.keys())
+        sb = set(b.keys())
+        common = sa & sb
+        changed = set()
+        for i in common:
+            if a[i] != b[i] and i not in whitelist:
+                changed.add(i)
+        added = sb - sa
+        removed = sa - sb
+        return changed, added, removed
+
+    def file_checksums_diff(a, b):
+        from collections import Counter
+        # Handle old siginfo format
+        if isinstance(a, dict):
+            a = [(os.path.basename(f), cs) for f, cs in a.items()]
+        if isinstance(b, dict):
+            b = [(os.path.basename(f), cs) for f, cs in b.items()]
+        # Compare lists, ensuring we can handle duplicate filenames if they exist
+        removedcount = Counter(a)
+        removedcount.subtract(b)
+        addedcount = Counter(b)
+        addedcount.subtract(a)
+        added = []
+        for x in b:
+            if addedcount[x] > 0:
+                addedcount[x] -= 1
+                added.append(x)
+        removed = []
+        changed = []
+        for x in a:
+            if removedcount[x] > 0:
+                removedcount[x] -= 1
+                for y in added:
+                    if y[0] == x[0]:
+                        changed.append((x[0], x[1], y[1]))
+                        added.remove(y)
+                        break
+                else:
+                    removed.append(x)
+        added = [x[0] for x in added]
+        removed = [x[0] for x in removed]
+        return changed, added, removed
+
+    if 'basewhitelist' in a_data and a_data['basewhitelist'] != b_data['basewhitelist']:
+        output.append("basewhitelist changed from '%s' to '%s'" % (a_data['basewhitelist'], b_data['basewhitelist']))
+        if a_data['basewhitelist'] and b_data['basewhitelist']:
+            output.append("changed items: %s" % a_data['basewhitelist'].symmetric_difference(b_data['basewhitelist']))
+
+    if 'taskwhitelist' in a_data and a_data['taskwhitelist'] != b_data['taskwhitelist']:
+        output.append("taskwhitelist changed from '%s' to '%s'" % (a_data['taskwhitelist'], b_data['taskwhitelist']))
+        if a_data['taskwhitelist'] and b_data['taskwhitelist']:
+            output.append("changed items: %s" % a_data['taskwhitelist'].symmetric_difference(b_data['taskwhitelist']))
+
+    if a_data['taskdeps'] != b_data['taskdeps']:
+        output.append("Task dependencies changed from:\n%s\nto:\n%s" % (sorted(a_data['taskdeps']), sorted(b_data['taskdeps'])))
+
+    if a_data['basehash'] != b_data['basehash']:
+        output.append("basehash changed from %s to %s" % (a_data['basehash'], b_data['basehash']))
+
+    changed, added, removed = dict_diff(a_data['gendeps'], b_data['gendeps'], a_data['basewhitelist'] & b_data['basewhitelist'])
+    if changed:
+        for dep in changed:
+            output.append("List of dependencies for variable %s changed from '%s' to '%s'" % (dep, a_data['gendeps'][dep], b_data['gendeps'][dep]))
+            if a_data['gendeps'][dep] and b_data['gendeps'][dep]:
+                output.append("changed items: %s" % a_data['gendeps'][dep].symmetric_difference(b_data['gendeps'][dep]))
+    if added:
+        for dep in added:
+            output.append("Dependency on variable %s was added" % (dep))
+    if removed:
+        for dep in removed:
+            output.append("Dependency on Variable %s was removed" % (dep))
+
+
+    changed, added, removed = dict_diff(a_data['varvals'], b_data['varvals'])
+    if changed:
+        for dep in changed:
+            output.append("Variable %s value changed from '%s' to '%s'" % (dep, a_data['varvals'][dep], b_data['varvals'][dep]))
+
+    changed, added, removed = file_checksums_diff(a_data['file_checksum_values'], b_data['file_checksum_values'])
+    if changed:
+        for f, old, new in changed:
+            output.append("Checksum for file %s changed from %s to %s" % (f, old, new))
+    if added:
+        for f in added:
+            output.append("Dependency on checksum of file %s was added" % (f))
+    if removed:
+        for f in removed:
+            output.append("Dependency on checksum of file %s was removed" % (f))
+
+
+    if len(a_data['runtaskdeps']) != len(b_data['runtaskdeps']):
+        changed = ["Number of task dependencies changed"]
+    else:
+        changed = []
+        for idx, task in enumerate(a_data['runtaskdeps']):
+            a = a_data['runtaskdeps'][idx]
+            b = b_data['runtaskdeps'][idx]
+            if a_data['runtaskhashes'][a] != b_data['runtaskhashes'][b]:
+                changed.append("%s with hash %s\n changed to\n%s with hash %s" % (a, a_data['runtaskhashes'][a], b, b_data['runtaskhashes'][b]))
+
+    if changed:
+        output.append("runtaskdeps changed from %s to %s" % (clean_basepaths_list(a_data['runtaskdeps']), clean_basepaths_list(b_data['runtaskdeps'])))
+        output.append("\n".join(changed))
+
+
+    if 'runtaskhashes' in a_data and 'runtaskhashes' in b_data:
+        a = a_data['runtaskhashes']
+        b = b_data['runtaskhashes']
+        changed, added, removed = dict_diff(a, b)
+        if added:
+            for dep in added:
+                bdep_found = False
+                if removed:
+                    for bdep in removed:
+                        if b[dep] == a[bdep]:
+                            #output.append("Dependency on task %s was replaced by %s with same hash" % (dep, bdep))
+                            bdep_found = True
+                if not bdep_found:
+                    output.append("Dependency on task %s was added with hash %s" % (clean_basepath(dep), b[dep]))
+        if removed:
+            for dep in removed:
+                adep_found = False
+                if added:
+                    for adep in added:
+                        if b[adep] == a[dep]:
+                            #output.append("Dependency on task %s was replaced by %s with same hash" % (adep, dep))
+                            adep_found = True
+                if not adep_found:
+                    output.append("Dependency on task %s was removed with hash %s" % (clean_basepath(dep), a[dep]))
+        if changed:
+            for dep in changed:
+                output.append("Hash for dependent task %s changed from %s to %s" % (clean_basepath(dep), a[dep], b[dep]))
+                if callable(recursecb):
+                    # If a dependent hash changed, might as well print the line above and then defer to the changes in 
+                    # that hash since in all likelyhood, they're the same changes this task also saw.
+                    recout = recursecb(dep, a[dep], b[dep])
+                    if recout:
+                        output = [output[-1]] + recout
+
+    a_taint = a_data.get('taint', None)
+    b_taint = b_data.get('taint', None)
+    if a_taint != b_taint:
+        output.append("Taint (by forced/invalidated task) changed from %s to %s" % (a_taint, b_taint))
+
+    return output
+
+
+def dump_sigfile(a):
+    output = []
+
+    p1 = pickle.Unpickler(open(a, "rb"))
+    a_data = p1.load()
+
+    output.append("basewhitelist: %s" % (a_data['basewhitelist']))
+
+    output.append("taskwhitelist: %s" % (a_data['taskwhitelist']))
+
+    output.append("Task dependencies: %s" % (sorted(a_data['taskdeps'])))
+
+    output.append("basehash: %s" % (a_data['basehash']))
+
+    for dep in a_data['gendeps']:
+        output.append("List of dependencies for variable %s is %s" % (dep, a_data['gendeps'][dep]))
+
+    for dep in a_data['varvals']:
+        output.append("Variable %s value is %s" % (dep, a_data['varvals'][dep]))
+
+    if 'runtaskdeps' in a_data:
+        output.append("Tasks this task depends on: %s" % (a_data['runtaskdeps']))
+
+    if 'file_checksum_values' in a_data:
+        output.append("This task depends on the checksums of files: %s" % (a_data['file_checksum_values']))
+
+    if 'runtaskhashes' in a_data:
+        for dep in a_data['runtaskhashes']:
+            output.append("Hash for dependent task %s is %s" % (dep, a_data['runtaskhashes'][dep]))
+
+    if 'taint' in a_data:
+        output.append("Tainted (by forced/invalidated task): %s" % a_data['taint'])
+
+    data = a_data['basehash']
+    for dep in a_data['runtaskdeps']:
+        data = data + a_data['runtaskhashes'][dep]
+
+    for c in a_data['file_checksum_values']:
+        data = data + c[1]
+
+    if 'taint' in a_data:
+        data = data + a_data['taint']
+
+    h = hashlib.md5(data).hexdigest()
+    output.append("Computed Hash is %s" % h)
+
+    return output
diff --git a/bitbake/lib/bb/taskdata.py b/bitbake/lib/bb/taskdata.py
new file mode 100644
index 0000000..5fab704
--- /dev/null
+++ b/bitbake/lib/bb/taskdata.py
@@ -0,0 +1,655 @@
+#!/usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake 'TaskData' implementation
+
+Task data collection and handling
+
+"""
+
+# Copyright (C) 2006  Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import logging
+import re
+import bb
+
+logger = logging.getLogger("BitBake.TaskData")
+
+def re_match_strings(target, strings):
+    """
+    Whether or not the string 'target' matches
+    any one string of the strings which can be regular expression string
+    """
+    return any(name == target or re.match(name, target)
+               for name in strings)
+
+class TaskData:
+    """
+    BitBake Task Data implementation
+    """
+    def __init__(self, abort = True, tryaltconfigs = False, skiplist = None, allowincomplete = False):
+        self.build_names_index = []
+        self.run_names_index = []
+        self.fn_index = []
+
+        self.build_targets = {}
+        self.run_targets = {}
+
+        self.external_targets = []
+
+        self.tasks_fnid = []
+        self.tasks_name = []
+        self.tasks_tdepends = []
+        self.tasks_idepends = []
+        self.tasks_irdepends = []
+        # Cache to speed up task ID lookups
+        self.tasks_lookup = {}
+
+        self.depids = {}
+        self.rdepids = {}
+
+        self.consider_msgs_cache = []
+
+        self.failed_deps = []
+        self.failed_rdeps = []
+        self.failed_fnids = []
+
+        self.abort = abort
+        self.tryaltconfigs = tryaltconfigs
+        self.allowincomplete = allowincomplete
+
+        self.skiplist = skiplist
+
+    def getbuild_id(self, name):
+        """
+        Return an ID number for the build target name.
+        If it doesn't exist, create one.
+        """
+        if not name in self.build_names_index:
+            self.build_names_index.append(name)
+            return len(self.build_names_index) - 1
+
+        return self.build_names_index.index(name)
+
+    def getrun_id(self, name):
+        """
+        Return an ID number for the run target name.
+        If it doesn't exist, create one.
+        """
+        if not name in self.run_names_index:
+            self.run_names_index.append(name)
+            return len(self.run_names_index) - 1
+
+        return self.run_names_index.index(name)
+
+    def getfn_id(self, name):
+        """
+        Return an ID number for the filename.
+        If it doesn't exist, create one.
+        """
+        if not name in self.fn_index:
+            self.fn_index.append(name)
+            return len(self.fn_index) - 1
+
+        return self.fn_index.index(name)
+
+    def gettask_ids(self, fnid):
+        """
+        Return an array of the ID numbers matching a given fnid.
+        """
+        ids = []
+        if fnid in self.tasks_lookup:
+            for task in self.tasks_lookup[fnid]:
+                ids.append(self.tasks_lookup[fnid][task])
+        return ids
+
+    def gettask_id_fromfnid(self, fnid, task):
+        """
+        Return an ID number for the task matching fnid and task.
+        """
+        if fnid in self.tasks_lookup:
+            if task in self.tasks_lookup[fnid]:
+                return self.tasks_lookup[fnid][task]
+
+        return None
+
+    def gettask_id(self, fn, task, create = True):
+        """
+        Return an ID number for the task matching fn and task.
+        If it doesn't exist, create one by default.
+        Optionally return None instead.
+        """
+        fnid = self.getfn_id(fn)
+
+        if fnid in self.tasks_lookup:
+            if task in self.tasks_lookup[fnid]:
+                return self.tasks_lookup[fnid][task]
+
+        if not create:
+            return None
+
+        self.tasks_name.append(task)
+        self.tasks_fnid.append(fnid)
+        self.tasks_tdepends.append([])
+        self.tasks_idepends.append([])
+        self.tasks_irdepends.append([])
+
+        listid = len(self.tasks_name) - 1
+
+        if fnid not in self.tasks_lookup:
+            self.tasks_lookup[fnid] = {}
+        self.tasks_lookup[fnid][task] = listid
+
+        return listid
+
+    def add_tasks(self, fn, dataCache):
+        """
+        Add tasks for a given fn to the database
+        """
+
+        task_deps = dataCache.task_deps[fn]
+
+        fnid = self.getfn_id(fn)
+
+        if fnid in self.failed_fnids:
+            bb.msg.fatal("TaskData", "Trying to re-add a failed file? Something is broken...")
+
+        # Check if we've already seen this fn
+        if fnid in self.tasks_fnid:
+            return
+
+        for task in task_deps['tasks']:
+
+            # Work out task dependencies
+            parentids = []
+            for dep in task_deps['parents'][task]:
+                if dep not in task_deps['tasks']:
+                    bb.debug(2, "Not adding dependeny of %s on %s since %s does not exist" % (task, dep, dep))
+                    continue
+                parentid = self.gettask_id(fn, dep)
+                parentids.append(parentid)
+            taskid = self.gettask_id(fn, task)
+            self.tasks_tdepends[taskid].extend(parentids)
+
+            # Touch all intertask dependencies
+            if 'depends' in task_deps and task in task_deps['depends']:
+                ids = []
+                for dep in task_deps['depends'][task].split():
+                    if dep:
+                        if ":" not in dep:
+                            bb.msg.fatal("TaskData", "Error for %s, dependency %s does not contain ':' character\n. Task 'depends' should be specified in the form 'packagename:task'" % (fn, dep))
+                        ids.append(((self.getbuild_id(dep.split(":")[0])), dep.split(":")[1]))
+                self.tasks_idepends[taskid].extend(ids)
+            if 'rdepends' in task_deps and task in task_deps['rdepends']:
+                ids = []
+                for dep in task_deps['rdepends'][task].split():
+                    if dep:
+                        if ":" not in dep:
+                            bb.msg.fatal("TaskData", "Error for %s, dependency %s does not contain ':' character\n. Task 'rdepends' should be specified in the form 'packagename:task'" % (fn, dep))
+                        ids.append(((self.getrun_id(dep.split(":")[0])), dep.split(":")[1]))
+                self.tasks_irdepends[taskid].extend(ids)
+
+
+        # Work out build dependencies
+        if not fnid in self.depids:
+            dependids = {}
+            for depend in dataCache.deps[fn]:
+                dependids[self.getbuild_id(depend)] = None
+            self.depids[fnid] = dependids.keys()
+            logger.debug(2, "Added dependencies %s for %s", str(dataCache.deps[fn]), fn)
+
+        # Work out runtime dependencies
+        if not fnid in self.rdepids:
+            rdependids = {}
+            rdepends = dataCache.rundeps[fn]
+            rrecs = dataCache.runrecs[fn]
+            rdependlist = []
+            rreclist = []
+            for package in rdepends:
+                for rdepend in rdepends[package]:
+                    rdependlist.append(rdepend)
+                    rdependids[self.getrun_id(rdepend)] = None
+            for package in rrecs:
+                for rdepend in rrecs[package]:
+                    rreclist.append(rdepend)
+                    rdependids[self.getrun_id(rdepend)] = None
+            if rdependlist:
+                logger.debug(2, "Added runtime dependencies %s for %s", str(rdependlist), fn)
+            if rreclist:
+                logger.debug(2, "Added runtime recommendations %s for %s", str(rreclist), fn)
+            self.rdepids[fnid] = rdependids.keys()
+
+        for dep in self.depids[fnid]:
+            if dep in self.failed_deps:
+                self.fail_fnid(fnid)
+                return
+        for dep in self.rdepids[fnid]:
+            if dep in self.failed_rdeps:
+                self.fail_fnid(fnid)
+                return
+
+    def have_build_target(self, target):
+        """
+        Have we a build target matching this name?
+        """
+        targetid = self.getbuild_id(target)
+
+        if targetid in self.build_targets:
+            return True
+        return False
+
+    def have_runtime_target(self, target):
+        """
+        Have we a runtime target matching this name?
+        """
+        targetid = self.getrun_id(target)
+
+        if targetid in self.run_targets:
+            return True
+        return False
+
+    def add_build_target(self, fn, item):
+        """
+        Add a build target.
+        If already present, append the provider fn to the list
+        """
+        targetid = self.getbuild_id(item)
+        fnid = self.getfn_id(fn)
+
+        if targetid in self.build_targets:
+            if fnid in self.build_targets[targetid]:
+                return
+            self.build_targets[targetid].append(fnid)
+            return
+        self.build_targets[targetid] = [fnid]
+
+    def add_runtime_target(self, fn, item):
+        """
+        Add a runtime target.
+        If already present, append the provider fn to the list
+        """
+        targetid = self.getrun_id(item)
+        fnid = self.getfn_id(fn)
+
+        if targetid in self.run_targets:
+            if fnid in self.run_targets[targetid]:
+                return
+            self.run_targets[targetid].append(fnid)
+            return
+        self.run_targets[targetid] = [fnid]
+
+    def mark_external_target(self, item):
+        """
+        Mark a build target as being externally requested
+        """
+        targetid = self.getbuild_id(item)
+
+        if targetid not in self.external_targets:
+            self.external_targets.append(targetid)
+
+    def get_unresolved_build_targets(self, dataCache):
+        """
+        Return a list of build targets who's providers
+        are unknown.
+        """
+        unresolved = []
+        for target in self.build_names_index:
+            if re_match_strings(target, dataCache.ignored_dependencies):
+                continue
+            if self.build_names_index.index(target) in self.failed_deps:
+                continue
+            if not self.have_build_target(target):
+                unresolved.append(target)
+        return unresolved
+
+    def get_unresolved_run_targets(self, dataCache):
+        """
+        Return a list of runtime targets who's providers
+        are unknown.
+        """
+        unresolved = []
+        for target in self.run_names_index:
+            if re_match_strings(target, dataCache.ignored_dependencies):
+                continue
+            if self.run_names_index.index(target) in self.failed_rdeps:
+                continue
+            if not self.have_runtime_target(target):
+                unresolved.append(target)
+        return unresolved
+
+    def get_provider(self, item):
+        """
+        Return a list of providers of item
+        """
+        targetid = self.getbuild_id(item)
+
+        return self.build_targets[targetid]
+
+    def get_dependees(self, itemid):
+        """
+        Return a list of targets which depend on item
+        """
+        dependees = []
+        for fnid in self.depids:
+            if itemid in self.depids[fnid]:
+                dependees.append(fnid)
+        return dependees
+
+    def get_dependees_str(self, item):
+        """
+        Return a list of targets which depend on item as a user readable string
+        """
+        itemid = self.getbuild_id(item)
+        dependees = []
+        for fnid in self.depids:
+            if itemid in self.depids[fnid]:
+                dependees.append(self.fn_index[fnid])
+        return dependees
+
+    def get_rdependees(self, itemid):
+        """
+        Return a list of targets which depend on runtime item
+        """
+        dependees = []
+        for fnid in self.rdepids:
+            if itemid in self.rdepids[fnid]:
+                dependees.append(fnid)
+        return dependees
+
+    def get_rdependees_str(self, item):
+        """
+        Return a list of targets which depend on runtime item as a user readable string
+        """
+        itemid = self.getrun_id(item)
+        dependees = []
+        for fnid in self.rdepids:
+            if itemid in self.rdepids[fnid]:
+                dependees.append(self.fn_index[fnid])
+        return dependees
+
+    def get_reasons(self, item, runtime=False):
+        """
+        Get the reason(s) for an item not being provided, if any
+        """
+        reasons = []
+        if self.skiplist:
+            for fn in self.skiplist:
+                skipitem = self.skiplist[fn]
+                if skipitem.pn == item:
+                    reasons.append("%s was skipped: %s" % (skipitem.pn, skipitem.skipreason))
+                elif runtime and item in skipitem.rprovides:
+                    reasons.append("%s RPROVIDES %s but was skipped: %s" % (skipitem.pn, item, skipitem.skipreason))
+                elif not runtime and item in skipitem.provides:
+                    reasons.append("%s PROVIDES %s but was skipped: %s" % (skipitem.pn, item, skipitem.skipreason))
+        return reasons
+
+    def get_close_matches(self, item, provider_list):
+        import difflib
+        if self.skiplist:
+            skipped = []
+            for fn in self.skiplist:
+                skipped.append(self.skiplist[fn].pn)
+            full_list = provider_list + skipped
+        else:
+            full_list = provider_list
+        return difflib.get_close_matches(item, full_list, cutoff=0.7)
+
+    def add_provider(self, cfgData, dataCache, item):
+        try:
+            self.add_provider_internal(cfgData, dataCache, item)
+        except bb.providers.NoProvider:
+            if self.abort:
+                raise
+            self.remove_buildtarget(self.getbuild_id(item))
+
+        self.mark_external_target(item)
+
+    def add_provider_internal(self, cfgData, dataCache, item):
+        """
+        Add the providers of item to the task data
+        Mark entries were specifically added externally as against dependencies
+        added internally during dependency resolution
+        """
+
+        if re_match_strings(item, dataCache.ignored_dependencies):
+            return
+
+        if not item in dataCache.providers:
+            bb.event.fire(bb.event.NoProvider(item, dependees=self.get_dependees_str(item), reasons=self.get_reasons(item), close_matches=self.get_close_matches(item, dataCache.providers.keys())), cfgData)
+            raise bb.providers.NoProvider(item)
+
+        if self.have_build_target(item):
+            return
+
+        all_p = dataCache.providers[item]
+
+        eligible, foundUnique = bb.providers.filterProviders(all_p, item, cfgData, dataCache)
+        eligible = [p for p in eligible if not self.getfn_id(p) in self.failed_fnids]
+
+        if not eligible:
+            bb.event.fire(bb.event.NoProvider(item, dependees=self.get_dependees_str(item), reasons=["No eligible PROVIDERs exist for '%s'" % item]), cfgData)
+            raise bb.providers.NoProvider(item)
+
+        if len(eligible) > 1 and foundUnique == False:
+            if item not in self.consider_msgs_cache:
+                providers_list = []
+                for fn in eligible:
+                    providers_list.append(dataCache.pkg_fn[fn])
+                bb.event.fire(bb.event.MultipleProviders(item, providers_list), cfgData)
+            self.consider_msgs_cache.append(item)
+
+        for fn in eligible:
+            fnid = self.getfn_id(fn)
+            if fnid in self.failed_fnids:
+                continue
+            logger.debug(2, "adding %s to satisfy %s", fn, item)
+            self.add_build_target(fn, item)
+            self.add_tasks(fn, dataCache)
+
+
+            #item = dataCache.pkg_fn[fn]
+
+    def add_rprovider(self, cfgData, dataCache, item):
+        """
+        Add the runtime providers of item to the task data
+        (takes item names from RDEPENDS/PACKAGES namespace)
+        """
+
+        if re_match_strings(item, dataCache.ignored_dependencies):
+            return
+
+        if self.have_runtime_target(item):
+            return
+
+        all_p = bb.providers.getRuntimeProviders(dataCache, item)
+
+        if not all_p:
+            bb.event.fire(bb.event.NoProvider(item, runtime=True, dependees=self.get_rdependees_str(item), reasons=self.get_reasons(item, True)), cfgData)
+            raise bb.providers.NoRProvider(item)
+
+        eligible, numberPreferred = bb.providers.filterProvidersRunTime(all_p, item, cfgData, dataCache)
+        eligible = [p for p in eligible if not self.getfn_id(p) in self.failed_fnids]
+
+        if not eligible:
+            bb.event.fire(bb.event.NoProvider(item, runtime=True, dependees=self.get_rdependees_str(item), reasons=["No eligible RPROVIDERs exist for '%s'" % item]), cfgData)
+            raise bb.providers.NoRProvider(item)
+
+        if len(eligible) > 1 and numberPreferred == 0:
+            if item not in self.consider_msgs_cache:
+                providers_list = []
+                for fn in eligible:
+                    providers_list.append(dataCache.pkg_fn[fn])
+                bb.event.fire(bb.event.MultipleProviders(item, providers_list, runtime=True), cfgData)
+            self.consider_msgs_cache.append(item)
+
+        if numberPreferred > 1:
+            if item not in self.consider_msgs_cache:
+                providers_list = []
+                for fn in eligible:
+                    providers_list.append(dataCache.pkg_fn[fn])
+                bb.event.fire(bb.event.MultipleProviders(item, providers_list, runtime=True), cfgData)
+            self.consider_msgs_cache.append(item)
+            raise bb.providers.MultipleRProvider(item)
+
+        # run through the list until we find one that we can build
+        for fn in eligible:
+            fnid = self.getfn_id(fn)
+            if fnid in self.failed_fnids:
+                continue
+            logger.debug(2, "adding '%s' to satisfy runtime '%s'", fn, item)
+            self.add_runtime_target(fn, item)
+            self.add_tasks(fn, dataCache)
+
+    def fail_fnid(self, fnid, missing_list=None):
+        """
+        Mark a file as failed (unbuildable)
+        Remove any references from build and runtime provider lists
+
+        missing_list, A list of missing requirements for this target
+        """
+        if fnid in self.failed_fnids:
+            return
+        if not missing_list:
+            missing_list = []
+        logger.debug(1, "File '%s' is unbuildable, removing...", self.fn_index[fnid])
+        self.failed_fnids.append(fnid)
+        for target in self.build_targets:
+            if fnid in self.build_targets[target]:
+                self.build_targets[target].remove(fnid)
+                if len(self.build_targets[target]) == 0:
+                    self.remove_buildtarget(target, missing_list)
+        for target in self.run_targets:
+            if fnid in self.run_targets[target]:
+                self.run_targets[target].remove(fnid)
+                if len(self.run_targets[target]) == 0:
+                    self.remove_runtarget(target, missing_list)
+
+    def remove_buildtarget(self, targetid, missing_list=None):
+        """
+        Mark a build target as failed (unbuildable)
+        Trigger removal of any files that have this as a dependency
+        """
+        if not missing_list:
+            missing_list = [self.build_names_index[targetid]]
+        else:
+            missing_list = [self.build_names_index[targetid]] + missing_list
+        logger.verbose("Target '%s' is unbuildable, removing...\nMissing or unbuildable dependency chain was: %s", self.build_names_index[targetid], missing_list)
+        self.failed_deps.append(targetid)
+        dependees = self.get_dependees(targetid)
+        for fnid in dependees:
+            self.fail_fnid(fnid, missing_list)
+        for taskid in xrange(len(self.tasks_idepends)):
+            idepends = self.tasks_idepends[taskid]
+            for (idependid, idependtask) in idepends:
+                if idependid == targetid:
+                    self.fail_fnid(self.tasks_fnid[taskid], missing_list)
+
+        if self.abort and targetid in self.external_targets:
+            target = self.build_names_index[targetid]
+            logger.error("Required build target '%s' has no buildable providers.\nMissing or unbuildable dependency chain was: %s", target, missing_list)
+            raise bb.providers.NoProvider(target)
+
+    def remove_runtarget(self, targetid, missing_list=None):
+        """
+        Mark a run target as failed (unbuildable)
+        Trigger removal of any files that have this as a dependency
+        """
+        if not missing_list:
+            missing_list = [self.run_names_index[targetid]]
+        else:
+            missing_list = [self.run_names_index[targetid]] + missing_list
+
+        logger.info("Runtime target '%s' is unbuildable, removing...\nMissing or unbuildable dependency chain was: %s", self.run_names_index[targetid], missing_list)
+        self.failed_rdeps.append(targetid)
+        dependees = self.get_rdependees(targetid)
+        for fnid in dependees:
+            self.fail_fnid(fnid, missing_list)
+        for taskid in xrange(len(self.tasks_irdepends)):
+            irdepends = self.tasks_irdepends[taskid]
+            for (idependid, idependtask) in irdepends:
+                if idependid == targetid:
+                    self.fail_fnid(self.tasks_fnid[taskid], missing_list)
+
+    def add_unresolved(self, cfgData, dataCache):
+        """
+        Resolve all unresolved build and runtime targets
+        """
+        logger.info("Resolving any missing task queue dependencies")
+        while True:
+            added = 0
+            for target in self.get_unresolved_build_targets(dataCache):
+                try:
+                    self.add_provider_internal(cfgData, dataCache, target)
+                    added = added + 1
+                except bb.providers.NoProvider:
+                    targetid = self.getbuild_id(target)
+                    if self.abort and targetid in self.external_targets and not self.allowincomplete:
+                        raise
+                    if not self.allowincomplete:
+                        self.remove_buildtarget(targetid)
+            for target in self.get_unresolved_run_targets(dataCache):
+                try:
+                    self.add_rprovider(cfgData, dataCache, target)
+                    added = added + 1
+                except (bb.providers.NoRProvider, bb.providers.MultipleRProvider):
+                    self.remove_runtarget(self.getrun_id(target))
+            logger.debug(1, "Resolved " + str(added) + " extra dependencies")
+            if added == 0:
+                break
+        # self.dump_data()
+
+    def dump_data(self):
+        """
+        Dump some debug information on the internal data structures
+        """
+        logger.debug(3, "build_names:")
+        logger.debug(3, ", ".join(self.build_names_index))
+
+        logger.debug(3, "run_names:")
+        logger.debug(3, ", ".join(self.run_names_index))
+
+        logger.debug(3, "build_targets:")
+        for buildid in xrange(len(self.build_names_index)):
+            target = self.build_names_index[buildid]
+            targets = "None"
+            if buildid in self.build_targets:
+                targets = self.build_targets[buildid]
+            logger.debug(3, " (%s)%s: %s", buildid, target, targets)
+
+        logger.debug(3, "run_targets:")
+        for runid in xrange(len(self.run_names_index)):
+            target = self.run_names_index[runid]
+            targets = "None"
+            if runid in self.run_targets:
+                targets = self.run_targets[runid]
+            logger.debug(3, " (%s)%s: %s", runid, target, targets)
+
+        logger.debug(3, "tasks:")
+        for task in xrange(len(self.tasks_name)):
+            logger.debug(3, " (%s)%s - %s: %s",
+                       task,
+                       self.fn_index[self.tasks_fnid[task]],
+                       self.tasks_name[task],
+                       self.tasks_tdepends[task])
+
+        logger.debug(3, "dependency ids (per fn):")
+        for fnid in self.depids:
+            logger.debug(3, " %s %s: %s", fnid, self.fn_index[fnid], self.depids[fnid])
+
+        logger.debug(3, "runtime dependency ids (per fn):")
+        for fnid in self.rdepids:
+            logger.debug(3, " %s %s: %s", fnid, self.fn_index[fnid], self.rdepids[fnid])
diff --git a/bitbake/lib/bb/tests/__init__.py b/bitbake/lib/bb/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bitbake/lib/bb/tests/__init__.py
diff --git a/bitbake/lib/bb/tests/codeparser.py b/bitbake/lib/bb/tests/codeparser.py
new file mode 100644
index 0000000..4454bc5
--- /dev/null
+++ b/bitbake/lib/bb/tests/codeparser.py
@@ -0,0 +1,375 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Test for codeparser.py
+#
+# Copyright (C) 2010 Chris Larson
+# Copyright (C) 2012 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+import unittest
+import logging
+import bb
+
+logger = logging.getLogger('BitBake.TestCodeParser')
+
+# bb.data references bb.parse but can't directly import due to circular dependencies.
+# Hack around it for now :( 
+import bb.parse
+import bb.data
+
+class ReferenceTest(unittest.TestCase):
+    def setUp(self):
+        self.d = bb.data.init()
+
+    def setEmptyVars(self, varlist):
+        for k in varlist:
+            self.d.setVar(k, "")
+
+    def setValues(self, values):
+        for k, v in values.items():
+            self.d.setVar(k, v)
+
+    def assertReferences(self, refs):
+        self.assertEqual(self.references, refs)
+
+    def assertExecs(self, execs):
+        self.assertEqual(self.execs, execs)
+
+class VariableReferenceTest(ReferenceTest):
+
+    def parseExpression(self, exp):
+        parsedvar = self.d.expandWithRefs(exp, None)
+        self.references = parsedvar.references
+
+    def test_simple_reference(self):
+        self.setEmptyVars(["FOO"])
+        self.parseExpression("${FOO}")
+        self.assertReferences(set(["FOO"]))
+
+    def test_nested_reference(self):
+        self.setEmptyVars(["BAR"])
+        self.d.setVar("FOO", "BAR")
+        self.parseExpression("${${FOO}}")
+        self.assertReferences(set(["FOO", "BAR"]))
+
+    def test_python_reference(self):
+        self.setEmptyVars(["BAR"])
+        self.parseExpression("${@bb.data.getVar('BAR', d, True) + 'foo'}")
+        self.assertReferences(set(["BAR"]))
+
+class ShellReferenceTest(ReferenceTest):
+
+    def parseExpression(self, exp):
+        parsedvar = self.d.expandWithRefs(exp, None)
+        parser = bb.codeparser.ShellParser("ParserTest", logger)
+        parser.parse_shell(parsedvar.value)
+
+        self.references = parsedvar.references
+        self.execs = parser.execs
+
+    def test_quotes_inside_assign(self):
+        self.parseExpression('foo=foo"bar"baz')
+        self.assertReferences(set([]))
+
+    def test_quotes_inside_arg(self):
+        self.parseExpression('sed s#"bar baz"#"alpha beta"#g')
+        self.assertExecs(set(["sed"]))
+
+    def test_arg_continuation(self):
+        self.parseExpression("sed -i -e s,foo,bar,g \\\n *.pc")
+        self.assertExecs(set(["sed"]))
+
+    def test_dollar_in_quoted(self):
+        self.parseExpression('sed -i -e "foo$" *.pc')
+        self.assertExecs(set(["sed"]))
+
+    def test_quotes_inside_arg_continuation(self):
+        self.setEmptyVars(["bindir", "D", "libdir"])
+        self.parseExpression("""
+sed -i -e s#"moc_location=.*$"#"moc_location=${bindir}/moc4"# \\
+-e s#"uic_location=.*$"#"uic_location=${bindir}/uic4"# \\
+${D}${libdir}/pkgconfig/*.pc
+""")
+        self.assertReferences(set(["bindir", "D", "libdir"]))
+
+    def test_assign_subshell_expansion(self):
+        self.parseExpression("foo=$(echo bar)")
+        self.assertExecs(set(["echo"]))
+
+    def test_shell_unexpanded(self):
+        self.setEmptyVars(["QT_BASE_NAME"])
+        self.parseExpression('echo "${QT_BASE_NAME}"')
+        self.assertExecs(set(["echo"]))
+        self.assertReferences(set(["QT_BASE_NAME"]))
+
+    def test_incomplete_varexp_single_quotes(self):
+        self.parseExpression("sed -i -e 's:IP{:I${:g' $pc")
+        self.assertExecs(set(["sed"]))
+
+
+    def test_until(self):
+        self.parseExpression("until false; do echo true; done")
+        self.assertExecs(set(["false", "echo"]))
+        self.assertReferences(set())
+
+    def test_case(self):
+        self.parseExpression("""
+case $foo in
+*)
+bar
+;;
+esac
+""")
+        self.assertExecs(set(["bar"]))
+        self.assertReferences(set())
+
+    def test_assign_exec(self):
+        self.parseExpression("a=b c='foo bar' alpha 1 2 3")
+        self.assertExecs(set(["alpha"]))
+
+    def test_redirect_to_file(self):
+        self.setEmptyVars(["foo"])
+        self.parseExpression("echo foo >${foo}/bar")
+        self.assertExecs(set(["echo"]))
+        self.assertReferences(set(["foo"]))
+
+    def test_heredoc(self):
+        self.setEmptyVars(["theta"])
+        self.parseExpression("""
+cat <<END
+alpha
+beta
+${theta}
+END
+""")
+        self.assertReferences(set(["theta"]))
+
+    def test_redirect_from_heredoc(self):
+        v = ["B", "SHADOW_MAILDIR", "SHADOW_MAILFILE", "SHADOW_UTMPDIR", "SHADOW_LOGDIR", "bindir"]
+        self.setEmptyVars(v)
+        self.parseExpression("""
+cat <<END >${B}/cachedpaths
+shadow_cv_maildir=${SHADOW_MAILDIR}
+shadow_cv_mailfile=${SHADOW_MAILFILE}
+shadow_cv_utmpdir=${SHADOW_UTMPDIR}
+shadow_cv_logdir=${SHADOW_LOGDIR}
+shadow_cv_passwd_dir=${bindir}
+END
+""")
+        self.assertReferences(set(v))
+        self.assertExecs(set(["cat"]))
+
+#    def test_incomplete_command_expansion(self):
+#        self.assertRaises(reftracker.ShellSyntaxError, reftracker.execs,
+#                          bbvalue.shparse("cp foo`", self.d), self.d)
+
+#    def test_rogue_dollarsign(self):
+#        self.setValues({"D" : "/tmp"})
+#        self.parseExpression("install -d ${D}$")
+#        self.assertReferences(set(["D"]))
+#        self.assertExecs(set(["install"]))
+
+
+class PythonReferenceTest(ReferenceTest):
+
+    def setUp(self):
+        self.d = bb.data.init()
+        if hasattr(bb.utils, "_context"):
+            self.context = bb.utils._context
+        else:
+            import __builtin__
+            self.context = __builtin__.__dict__
+
+    def parseExpression(self, exp):
+        parsedvar = self.d.expandWithRefs(exp, None)
+        parser = bb.codeparser.PythonParser("ParserTest", logger)
+        parser.parse_python(parsedvar.value)
+
+        self.references = parsedvar.references | parser.references
+        self.execs = parser.execs
+
+    @staticmethod
+    def indent(value):
+        """Python Snippets have to be indented, python values don't have to
+be. These unit tests are testing snippets."""
+        return " " + value
+
+    def test_getvar_reference(self):
+        self.parseExpression("bb.data.getVar('foo', d, True)")
+        self.assertReferences(set(["foo"]))
+        self.assertExecs(set())
+
+    def test_getvar_computed_reference(self):
+        self.parseExpression("bb.data.getVar('f' + 'o' + 'o', d, True)")
+        self.assertReferences(set())
+        self.assertExecs(set())
+
+    def test_getvar_exec_reference(self):
+        self.parseExpression("eval('bb.data.getVar(\"foo\", d, True)')")
+        self.assertReferences(set())
+        self.assertExecs(set(["eval"]))
+
+    def test_var_reference(self):
+        self.context["foo"] = lambda x: x
+        self.setEmptyVars(["FOO"])
+        self.parseExpression("foo('${FOO}')")
+        self.assertReferences(set(["FOO"]))
+        self.assertExecs(set(["foo"]))
+        del self.context["foo"]
+
+    def test_var_exec(self):
+        for etype in ("func", "task"):
+            self.d.setVar("do_something", "echo 'hi mom! ${FOO}'")
+            self.d.setVarFlag("do_something", etype, True)
+            self.parseExpression("bb.build.exec_func('do_something', d)")
+            self.assertReferences(set([]))
+            self.assertExecs(set(["do_something"]))
+
+    def test_function_reference(self):
+        self.context["testfunc"] = lambda msg: bb.msg.note(1, None, msg)
+        self.d.setVar("FOO", "Hello, World!")
+        self.parseExpression("testfunc('${FOO}')")
+        self.assertReferences(set(["FOO"]))
+        self.assertExecs(set(["testfunc"]))
+        del self.context["testfunc"]
+
+    def test_qualified_function_reference(self):
+        self.parseExpression("time.time()")
+        self.assertExecs(set(["time.time"]))
+
+    def test_qualified_function_reference_2(self):
+        self.parseExpression("os.path.dirname('/foo/bar')")
+        self.assertExecs(set(["os.path.dirname"]))
+
+    def test_qualified_function_reference_nested(self):
+        self.parseExpression("time.strftime('%Y%m%d',time.gmtime())")
+        self.assertExecs(set(["time.strftime", "time.gmtime"]))
+
+    def test_function_reference_chained(self):
+        self.context["testget"] = lambda: "\tstrip me "
+        self.parseExpression("testget().strip()")
+        self.assertExecs(set(["testget"]))
+        del self.context["testget"]
+
+
+class DependencyReferenceTest(ReferenceTest):
+
+    pydata = """
+bb.data.getVar('somevar', d, True)
+def test(d):
+    foo = 'bar %s' % 'foo'
+def test2(d):
+    d.getVar(foo, True)
+    d.getVar('bar', False)
+    test2(d)
+
+def a():
+    \"\"\"some
+    stuff
+    \"\"\"
+    return "heh"
+
+test(d)
+
+bb.data.expand(bb.data.getVar("something", False, d), d)
+bb.data.expand("${inexpand} somethingelse", d)
+bb.data.getVar(a(), d, False)
+"""
+
+    def test_python(self):
+        self.d.setVar("FOO", self.pydata)
+        self.setEmptyVars(["inexpand", "a", "test2", "test"])
+        self.d.setVarFlags("FOO", {"func": True, "python": True})
+
+        deps, values = bb.data.build_dependencies("FOO", set(self.d.keys()), set(), set(), self.d)
+
+        self.assertEquals(deps, set(["somevar", "bar", "something", "inexpand", "test", "test2", "a"]))
+
+
+    shelldata = """
+foo () {
+bar
+}
+{
+echo baz
+$(heh)
+eval `moo`
+}
+a=b
+c=d
+(
+true && false
+test -f foo
+testval=something
+$testval
+) || aiee
+! inverted
+echo ${somevar}
+
+case foo in
+bar)
+echo bar
+;;
+baz)
+echo baz
+;;
+foo*)
+echo foo
+;;
+esac
+"""
+
+    def test_shell(self):
+        execs = ["bar", "echo", "heh", "moo", "true", "aiee"]
+        self.d.setVar("somevar", "heh")
+        self.d.setVar("inverted", "echo inverted...")
+        self.d.setVarFlag("inverted", "func", True)
+        self.d.setVar("FOO", self.shelldata)
+        self.d.setVarFlags("FOO", {"func": True})
+        self.setEmptyVars(execs)
+
+        deps, values = bb.data.build_dependencies("FOO", set(self.d.keys()), set(), set(), self.d)
+
+        self.assertEquals(deps, set(["somevar", "inverted"] + execs))
+
+
+    def test_vardeps(self):
+        self.d.setVar("oe_libinstall", "echo test")
+        self.d.setVar("FOO", "foo=oe_libinstall; eval $foo")
+        self.d.setVarFlag("FOO", "vardeps", "oe_libinstall")
+
+        deps, values = bb.data.build_dependencies("FOO", set(self.d.keys()), set(), set(), self.d)
+
+        self.assertEquals(deps, set(["oe_libinstall"]))
+
+    def test_vardeps_expand(self):
+        self.d.setVar("oe_libinstall", "echo test")
+        self.d.setVar("FOO", "foo=oe_libinstall; eval $foo")
+        self.d.setVarFlag("FOO", "vardeps", "${@'oe_libinstall'}")
+
+        deps, values = bb.data.build_dependencies("FOO", set(self.d.keys()), set(), set(), self.d)
+
+        self.assertEquals(deps, set(["oe_libinstall"]))
+
+    #Currently no wildcard support
+    #def test_vardeps_wildcards(self):
+    #    self.d.setVar("oe_libinstall", "echo test")
+    #    self.d.setVar("FOO", "foo=oe_libinstall; eval $foo")
+    #    self.d.setVarFlag("FOO", "vardeps", "oe_*")
+    #    self.assertEquals(deps, set(["oe_libinstall"]))
+
+
diff --git a/bitbake/lib/bb/tests/cow.py b/bitbake/lib/bb/tests/cow.py
new file mode 100644
index 0000000..35c5841
--- /dev/null
+++ b/bitbake/lib/bb/tests/cow.py
@@ -0,0 +1,136 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Tests for Copy-on-Write (cow.py)
+#
+# Copyright 2006 Holger Freyther <freyther@handhelds.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+import unittest
+import os
+
+class COWTestCase(unittest.TestCase):
+    """
+    Test case for the COW module from mithro
+    """
+
+    def testGetSet(self):
+        """
+        Test and set
+        """
+        from bb.COW import COWDictBase
+        a = COWDictBase.copy()
+
+        self.assertEquals(False, a.has_key('a'))
+
+        a['a'] = 'a'
+        a['b'] = 'b'
+        self.assertEquals(True, a.has_key('a'))
+        self.assertEquals(True, a.has_key('b'))
+        self.assertEquals('a', a['a'] )
+        self.assertEquals('b', a['b'] )
+
+    def testCopyCopy(self):
+        """
+        Test the copy of copies
+        """
+
+        from bb.COW import COWDictBase
+
+        # create two COW dict 'instances'
+        b = COWDictBase.copy()
+        c = COWDictBase.copy()
+
+        # assign some keys to one instance, some keys to another
+        b['a'] = 10
+        b['c'] = 20
+        c['a'] = 30
+
+        # test separation of the two instances
+        self.assertEquals(False, c.has_key('c'))
+        self.assertEquals(30, c['a'])
+        self.assertEquals(10, b['a'])
+
+        # test copy
+        b_2 = b.copy()
+        c_2 = c.copy()
+
+        self.assertEquals(False, c_2.has_key('c'))
+        self.assertEquals(10, b_2['a'])
+
+        b_2['d'] = 40
+        self.assertEquals(False, c_2.has_key('d'))
+        self.assertEquals(True, b_2.has_key('d'))
+        self.assertEquals(40, b_2['d'])
+        self.assertEquals(False, b.has_key('d'))
+        self.assertEquals(False, c.has_key('d'))
+
+        c_2['d'] = 30
+        self.assertEquals(True, c_2.has_key('d'))
+        self.assertEquals(True, b_2.has_key('d'))
+        self.assertEquals(30, c_2['d'])
+        self.assertEquals(40, b_2['d'])
+        self.assertEquals(False, b.has_key('d'))
+        self.assertEquals(False, c.has_key('d'))
+
+        # test copy of the copy
+        c_3 = c_2.copy()
+        b_3 = b_2.copy()
+        b_3_2 = b_2.copy()
+
+        c_3['e'] = 4711
+        self.assertEquals(4711, c_3['e'])
+        self.assertEquals(False, c_2.has_key('e'))
+        self.assertEquals(False, b_3.has_key('e'))
+        self.assertEquals(False, b_3_2.has_key('e'))
+        self.assertEquals(False, b_2.has_key('e'))
+
+        b_3['e'] = 'viel'
+        self.assertEquals('viel', b_3['e'])
+        self.assertEquals(4711, c_3['e'])
+        self.assertEquals(False, c_2.has_key('e'))
+        self.assertEquals(True, b_3.has_key('e'))
+        self.assertEquals(False, b_3_2.has_key('e'))
+        self.assertEquals(False, b_2.has_key('e'))
+
+    def testCow(self):
+        from bb.COW import COWDictBase
+        c = COWDictBase.copy()
+        c['123'] = 1027
+        c['other'] = 4711
+        c['d'] = { 'abc' : 10, 'bcd' : 20 }
+
+        copy = c.copy()
+
+        self.assertEquals(1027, c['123'])
+        self.assertEquals(4711, c['other'])
+        self.assertEquals({'abc':10, 'bcd':20}, c['d'])
+        self.assertEquals(1027, copy['123'])
+        self.assertEquals(4711, copy['other'])
+        self.assertEquals({'abc':10, 'bcd':20}, copy['d'])
+
+        # cow it now
+        copy['123'] = 1028
+        copy['other'] = 4712
+        copy['d']['abc'] = 20
+
+
+        self.assertEquals(1027, c['123'])
+        self.assertEquals(4711, c['other'])
+        self.assertEquals({'abc':10, 'bcd':20}, c['d'])
+        self.assertEquals(1028, copy['123'])
+        self.assertEquals(4712, copy['other'])
+        self.assertEquals({'abc':20, 'bcd':20}, copy['d'])
diff --git a/bitbake/lib/bb/tests/data.py b/bitbake/lib/bb/tests/data.py
new file mode 100644
index 0000000..e9aab57
--- /dev/null
+++ b/bitbake/lib/bb/tests/data.py
@@ -0,0 +1,441 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Tests for the Data Store (data.py/data_smart.py)
+#
+# Copyright (C) 2010 Chris Larson
+# Copyright (C) 2012 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+import unittest
+import bb
+import bb.data
+import bb.parse
+import logging
+
+class LogRecord():
+    def __enter__(self):
+        logs = []
+        class LogHandler(logging.Handler):
+            def emit(self, record):
+                logs.append(record)
+        logger = logging.getLogger("BitBake")
+        handler = LogHandler()
+        self.handler = handler
+        logger.addHandler(handler)
+        return logs
+    def __exit__(self, type, value, traceback):
+        logger = logging.getLogger("BitBake")
+        logger.removeHandler(self.handler)
+        return
+
+def logContains(item, logs):
+    for l in logs:
+        m = l.getMessage()
+        if item in m:
+            return True
+    return False
+
+class DataExpansions(unittest.TestCase):
+    def setUp(self):
+        self.d = bb.data.init()
+        self.d["foo"] = "value_of_foo"
+        self.d["bar"] = "value_of_bar"
+        self.d["value_of_foo"] = "value_of_'value_of_foo'"
+
+    def test_one_var(self):
+        val = self.d.expand("${foo}")
+        self.assertEqual(str(val), "value_of_foo")
+
+    def test_indirect_one_var(self):
+        val = self.d.expand("${${foo}}")
+        self.assertEqual(str(val), "value_of_'value_of_foo'")
+
+    def test_indirect_and_another(self):
+        val = self.d.expand("${${foo}} ${bar}")
+        self.assertEqual(str(val), "value_of_'value_of_foo' value_of_bar")
+
+    def test_python_snippet(self):
+        val = self.d.expand("${@5*12}")
+        self.assertEqual(str(val), "60")
+
+    def test_expand_in_python_snippet(self):
+        val = self.d.expand("${@'boo ' + '${foo}'}")
+        self.assertEqual(str(val), "boo value_of_foo")
+
+    def test_python_snippet_getvar(self):
+        val = self.d.expand("${@d.getVar('foo', True) + ' ${bar}'}")
+        self.assertEqual(str(val), "value_of_foo value_of_bar")
+
+    def test_python_snippet_syntax_error(self):
+        self.d.setVar("FOO", "${@foo = 5}")
+        self.assertRaises(bb.data_smart.ExpansionError, self.d.getVar, "FOO", True)
+
+    def test_python_snippet_runtime_error(self):
+        self.d.setVar("FOO", "${@int('test')}")
+        self.assertRaises(bb.data_smart.ExpansionError, self.d.getVar, "FOO", True)
+
+    def test_python_snippet_error_path(self):
+        self.d.setVar("FOO", "foo value ${BAR}")
+        self.d.setVar("BAR", "bar value ${@int('test')}")
+        self.assertRaises(bb.data_smart.ExpansionError, self.d.getVar, "FOO", True)
+
+    def test_value_containing_value(self):
+        val = self.d.expand("${@d.getVar('foo', True) + ' ${bar}'}")
+        self.assertEqual(str(val), "value_of_foo value_of_bar")
+
+    def test_reference_undefined_var(self):
+        val = self.d.expand("${undefinedvar} meh")
+        self.assertEqual(str(val), "${undefinedvar} meh")
+
+    def test_double_reference(self):
+        self.d.setVar("BAR", "bar value")
+        self.d.setVar("FOO", "${BAR} foo ${BAR}")
+        val = self.d.getVar("FOO", True)
+        self.assertEqual(str(val), "bar value foo bar value")
+
+    def test_direct_recursion(self):
+        self.d.setVar("FOO", "${FOO}")
+        self.assertRaises(bb.data_smart.ExpansionError, self.d.getVar, "FOO", True)
+
+    def test_indirect_recursion(self):
+        self.d.setVar("FOO", "${BAR}")
+        self.d.setVar("BAR", "${BAZ}")
+        self.d.setVar("BAZ", "${FOO}")
+        self.assertRaises(bb.data_smart.ExpansionError, self.d.getVar, "FOO", True)
+
+    def test_recursion_exception(self):
+        self.d.setVar("FOO", "${BAR}")
+        self.d.setVar("BAR", "${${@'FOO'}}")
+        self.assertRaises(bb.data_smart.ExpansionError, self.d.getVar, "FOO", True)
+
+    def test_incomplete_varexp_single_quotes(self):
+        self.d.setVar("FOO", "sed -i -e 's:IP{:I${:g' $pc")
+        val = self.d.getVar("FOO", True)
+        self.assertEqual(str(val), "sed -i -e 's:IP{:I${:g' $pc")
+
+    def test_nonstring(self):
+        self.d.setVar("TEST", 5)
+        val = self.d.getVar("TEST", True)
+        self.assertEqual(str(val), "5")
+
+    def test_rename(self):
+        self.d.renameVar("foo", "newfoo")
+        self.assertEqual(self.d.getVar("newfoo", False), "value_of_foo")
+        self.assertEqual(self.d.getVar("foo", False), None)
+
+    def test_deletion(self):
+        self.d.delVar("foo")
+        self.assertEqual(self.d.getVar("foo", False), None)
+
+    def test_keys(self):
+        keys = self.d.keys()
+        self.assertEqual(keys, ['value_of_foo', 'foo', 'bar'])
+
+    def test_keys_deletion(self):
+        newd = bb.data.createCopy(self.d)
+        newd.delVar("bar")
+        keys = newd.keys()
+        self.assertEqual(keys, ['value_of_foo', 'foo'])
+
+class TestNestedExpansions(unittest.TestCase):
+    def setUp(self):
+        self.d = bb.data.init()
+        self.d["foo"] = "foo"
+        self.d["bar"] = "bar"
+        self.d["value_of_foobar"] = "187"
+
+    def test_refs(self):
+        val = self.d.expand("${value_of_${foo}${bar}}")
+        self.assertEqual(str(val), "187")
+
+    #def test_python_refs(self):
+    #    val = self.d.expand("${@${@3}**2 + ${@4}**2}")
+    #    self.assertEqual(str(val), "25")
+
+    def test_ref_in_python_ref(self):
+        val = self.d.expand("${@'${foo}' + 'bar'}")
+        self.assertEqual(str(val), "foobar")
+
+    def test_python_ref_in_ref(self):
+        val = self.d.expand("${${@'f'+'o'+'o'}}")
+        self.assertEqual(str(val), "foo")
+
+    def test_deep_nesting(self):
+        depth = 100
+        val = self.d.expand("${" * depth + "foo" + "}" * depth)
+        self.assertEqual(str(val), "foo")
+
+    #def test_deep_python_nesting(self):
+    #    depth = 50
+    #    val = self.d.expand("${@" * depth + "1" + "+1}" * depth)
+    #    self.assertEqual(str(val), str(depth + 1))
+
+    def test_mixed(self):
+        val = self.d.expand("${value_of_${@('${foo}'+'bar')[0:3]}${${@'BAR'.lower()}}}")
+        self.assertEqual(str(val), "187")
+
+    def test_runtime(self):
+        val = self.d.expand("${${@'value_of' + '_f'+'o'+'o'+'b'+'a'+'r'}}")
+        self.assertEqual(str(val), "187")
+
+class TestMemoize(unittest.TestCase):
+    def test_memoized(self):
+        d = bb.data.init()
+        d.setVar("FOO", "bar")
+        self.assertTrue(d.getVar("FOO", False) is d.getVar("FOO", False))
+
+    def test_not_memoized(self):
+        d1 = bb.data.init()
+        d2 = bb.data.init()
+        d1.setVar("FOO", "bar")
+        d2.setVar("FOO", "bar2")
+        self.assertTrue(d1.getVar("FOO", False) is not d2.getVar("FOO", False))
+
+    def test_changed_after_memoized(self):
+        d = bb.data.init()
+        d.setVar("foo", "value of foo")
+        self.assertEqual(str(d.getVar("foo", False)), "value of foo")
+        d.setVar("foo", "second value of foo")
+        self.assertEqual(str(d.getVar("foo", False)), "second value of foo")
+
+    def test_same_value(self):
+        d = bb.data.init()
+        d.setVar("foo", "value of")
+        d.setVar("bar", "value of")
+        self.assertEqual(d.getVar("foo", False),
+                         d.getVar("bar", False))
+
+class TestConcat(unittest.TestCase):
+    def setUp(self):
+        self.d = bb.data.init()
+        self.d.setVar("FOO", "foo")
+        self.d.setVar("VAL", "val")
+        self.d.setVar("BAR", "bar")
+
+    def test_prepend(self):
+        self.d.setVar("TEST", "${VAL}")
+        self.d.prependVar("TEST", "${FOO}:")
+        self.assertEqual(self.d.getVar("TEST", True), "foo:val")
+
+    def test_append(self):
+        self.d.setVar("TEST", "${VAL}")
+        self.d.appendVar("TEST", ":${BAR}")
+        self.assertEqual(self.d.getVar("TEST", True), "val:bar")
+
+    def test_multiple_append(self):
+        self.d.setVar("TEST", "${VAL}")
+        self.d.prependVar("TEST", "${FOO}:")
+        self.d.appendVar("TEST", ":val2")
+        self.d.appendVar("TEST", ":${BAR}")
+        self.assertEqual(self.d.getVar("TEST", True), "foo:val:val2:bar")
+
+class TestConcatOverride(unittest.TestCase):
+    def setUp(self):
+        self.d = bb.data.init()
+        self.d.setVar("FOO", "foo")
+        self.d.setVar("VAL", "val")
+        self.d.setVar("BAR", "bar")
+
+    def test_prepend(self):
+        self.d.setVar("TEST", "${VAL}")
+        self.d.setVar("TEST_prepend", "${FOO}:")
+        bb.data.update_data(self.d)
+        self.assertEqual(self.d.getVar("TEST", True), "foo:val")
+
+    def test_append(self):
+        self.d.setVar("TEST", "${VAL}")
+        self.d.setVar("TEST_append", ":${BAR}")
+        bb.data.update_data(self.d)
+        self.assertEqual(self.d.getVar("TEST", True), "val:bar")
+
+    def test_multiple_append(self):
+        self.d.setVar("TEST", "${VAL}")
+        self.d.setVar("TEST_prepend", "${FOO}:")
+        self.d.setVar("TEST_append", ":val2")
+        self.d.setVar("TEST_append", ":${BAR}")
+        bb.data.update_data(self.d)
+        self.assertEqual(self.d.getVar("TEST", True), "foo:val:val2:bar")
+
+    def test_append_unset(self):
+        self.d.setVar("TEST_prepend", "${FOO}:")
+        self.d.setVar("TEST_append", ":val2")
+        self.d.setVar("TEST_append", ":${BAR}")
+        bb.data.update_data(self.d)
+        self.assertEqual(self.d.getVar("TEST", True), "foo::val2:bar")
+
+    def test_remove(self):
+        self.d.setVar("TEST", "${VAL} ${BAR}")
+        self.d.setVar("TEST_remove", "val")
+        bb.data.update_data(self.d)
+        self.assertEqual(self.d.getVar("TEST", True), "bar")
+
+    def test_doubleref_remove(self):
+        self.d.setVar("TEST", "${VAL} ${BAR}")
+        self.d.setVar("TEST_remove", "val")
+        self.d.setVar("TEST_TEST", "${TEST} ${TEST}")
+        bb.data.update_data(self.d)
+        self.assertEqual(self.d.getVar("TEST_TEST", True), "bar bar")
+
+    def test_empty_remove(self):
+        self.d.setVar("TEST", "")
+        self.d.setVar("TEST_remove", "val")
+        bb.data.update_data(self.d)
+        self.assertEqual(self.d.getVar("TEST", True), "")
+
+    def test_remove_expansion(self):
+        self.d.setVar("BAR", "Z")
+        self.d.setVar("TEST", "${BAR}/X Y")
+        self.d.setVar("TEST_remove", "${BAR}/X")
+        bb.data.update_data(self.d)
+        self.assertEqual(self.d.getVar("TEST", True), "Y")
+
+    def test_remove_expansion_items(self):
+        self.d.setVar("TEST", "A B C D")
+        self.d.setVar("BAR", "B D")
+        self.d.setVar("TEST_remove", "${BAR}")
+        bb.data.update_data(self.d)
+        self.assertEqual(self.d.getVar("TEST", True), "A C")
+
+class TestOverrides(unittest.TestCase):
+    def setUp(self):
+        self.d = bb.data.init()
+        self.d.setVar("OVERRIDES", "foo:bar:local")
+        self.d.setVar("TEST", "testvalue")
+
+    def test_no_override(self):
+        bb.data.update_data(self.d)
+        self.assertEqual(self.d.getVar("TEST", True), "testvalue")
+
+    def test_one_override(self):
+        self.d.setVar("TEST_bar", "testvalue2")
+        bb.data.update_data(self.d)
+        self.assertEqual(self.d.getVar("TEST", True), "testvalue2")
+
+    def test_one_override_unset(self):
+        self.d.setVar("TEST2_bar", "testvalue2")
+        bb.data.update_data(self.d)
+        self.assertEqual(self.d.getVar("TEST2", True), "testvalue2")
+        self.assertItemsEqual(self.d.keys(), ['TEST', 'TEST2', 'OVERRIDES', 'TEST2_bar'])
+
+    def test_multiple_override(self):
+        self.d.setVar("TEST_bar", "testvalue2")
+        self.d.setVar("TEST_local", "testvalue3")
+        self.d.setVar("TEST_foo", "testvalue4")
+        bb.data.update_data(self.d)
+        self.assertEqual(self.d.getVar("TEST", True), "testvalue3")
+        self.assertItemsEqual(self.d.keys(), ['TEST', 'TEST_foo', 'OVERRIDES', 'TEST_bar', 'TEST_local'])
+
+    def test_multiple_combined_overrides(self):
+        self.d.setVar("TEST_local_foo_bar", "testvalue3")
+        bb.data.update_data(self.d)
+        self.assertEqual(self.d.getVar("TEST", True), "testvalue3")
+
+    def test_multiple_overrides_unset(self):
+        self.d.setVar("TEST2_local_foo_bar", "testvalue3")
+        bb.data.update_data(self.d)
+        self.assertEqual(self.d.getVar("TEST2", True), "testvalue3")
+
+    def test_keyexpansion_override(self):
+        self.d.setVar("LOCAL", "local")
+        self.d.setVar("TEST_bar", "testvalue2")
+        self.d.setVar("TEST_${LOCAL}", "testvalue3")
+        self.d.setVar("TEST_foo", "testvalue4")
+        bb.data.update_data(self.d)
+        bb.data.expandKeys(self.d)
+        self.assertEqual(self.d.getVar("TEST", True), "testvalue3")
+
+    def test_rename_override(self):
+        self.d.setVar("ALTERNATIVE_ncurses-tools_class-target", "a")
+        self.d.setVar("OVERRIDES", "class-target")
+        bb.data.update_data(self.d)
+        self.d.renameVar("ALTERNATIVE_ncurses-tools", "ALTERNATIVE_lib32-ncurses-tools")
+        self.assertEqual(self.d.getVar("ALTERNATIVE_lib32-ncurses-tools", True), "a")
+
+    def test_underscore_override(self):
+        self.d.setVar("TEST_bar", "testvalue2")
+        self.d.setVar("TEST_some_val", "testvalue3")
+        self.d.setVar("TEST_foo", "testvalue4")
+        self.d.setVar("OVERRIDES", "foo:bar:some_val")
+        self.assertEqual(self.d.getVar("TEST", True), "testvalue3")
+
+class TestKeyExpansion(unittest.TestCase):
+    def setUp(self):
+        self.d = bb.data.init()
+        self.d.setVar("FOO", "foo")
+        self.d.setVar("BAR", "foo")
+
+    def test_keyexpand(self):
+        self.d.setVar("VAL_${FOO}", "A")
+        self.d.setVar("VAL_${BAR}", "B")
+        with LogRecord() as logs:
+            bb.data.expandKeys(self.d)
+            self.assertTrue(logContains("Variable key VAL_${FOO} (A) replaces original key VAL_foo (B)", logs))
+        self.assertEqual(self.d.getVar("VAL_foo", True), "A")
+
+class TestFlags(unittest.TestCase):
+    def setUp(self):
+        self.d = bb.data.init()
+        self.d.setVar("foo", "value of foo")
+        self.d.setVarFlag("foo", "flag1", "value of flag1")
+        self.d.setVarFlag("foo", "flag2", "value of flag2")
+
+    def test_setflag(self):
+        self.assertEqual(self.d.getVarFlag("foo", "flag1"), "value of flag1")
+        self.assertEqual(self.d.getVarFlag("foo", "flag2"), "value of flag2")
+
+    def test_delflag(self):
+        self.d.delVarFlag("foo", "flag2")
+        self.assertEqual(self.d.getVarFlag("foo", "flag1"), "value of flag1")
+        self.assertEqual(self.d.getVarFlag("foo", "flag2"), None)
+
+
+class Contains(unittest.TestCase):
+    def setUp(self):
+        self.d = bb.data.init()
+        self.d.setVar("SOMEFLAG", "a b c")
+
+    def test_contains(self):
+        self.assertTrue(bb.utils.contains("SOMEFLAG", "a", True, False, self.d))
+        self.assertTrue(bb.utils.contains("SOMEFLAG", "b", True, False, self.d))
+        self.assertTrue(bb.utils.contains("SOMEFLAG", "c", True, False, self.d))
+
+        self.assertTrue(bb.utils.contains("SOMEFLAG", "a b", True, False, self.d))
+        self.assertTrue(bb.utils.contains("SOMEFLAG", "b c", True, False, self.d))
+        self.assertTrue(bb.utils.contains("SOMEFLAG", "c a", True, False, self.d))
+
+        self.assertTrue(bb.utils.contains("SOMEFLAG", "a b c", True, False, self.d))
+        self.assertTrue(bb.utils.contains("SOMEFLAG", "c b a", True, False, self.d))
+
+        self.assertFalse(bb.utils.contains("SOMEFLAG", "x", True, False, self.d))
+        self.assertFalse(bb.utils.contains("SOMEFLAG", "a x", True, False, self.d))
+        self.assertFalse(bb.utils.contains("SOMEFLAG", "x c b", True, False, self.d))
+        self.assertFalse(bb.utils.contains("SOMEFLAG", "x c b a", True, False, self.d))
+
+    def test_contains_any(self):
+        self.assertTrue(bb.utils.contains_any("SOMEFLAG", "a", True, False, self.d))
+        self.assertTrue(bb.utils.contains_any("SOMEFLAG", "b", True, False, self.d))
+        self.assertTrue(bb.utils.contains_any("SOMEFLAG", "c", True, False, self.d))
+
+        self.assertTrue(bb.utils.contains_any("SOMEFLAG", "a b", True, False, self.d))
+        self.assertTrue(bb.utils.contains_any("SOMEFLAG", "b c", True, False, self.d))
+        self.assertTrue(bb.utils.contains_any("SOMEFLAG", "c a", True, False, self.d))
+
+        self.assertTrue(bb.utils.contains_any("SOMEFLAG", "a x", True, False, self.d))
+        self.assertTrue(bb.utils.contains_any("SOMEFLAG", "x c", True, False, self.d))
+
+        self.assertFalse(bb.utils.contains_any("SOMEFLAG", "x", True, False, self.d))
+        self.assertFalse(bb.utils.contains_any("SOMEFLAG", "x y z", True, False, self.d))
diff --git a/bitbake/lib/bb/tests/fetch.py b/bitbake/lib/bb/tests/fetch.py
new file mode 100644
index 0000000..1e61f3a
--- /dev/null
+++ b/bitbake/lib/bb/tests/fetch.py
@@ -0,0 +1,759 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Tests for the Fetcher (fetch2/)
+#
+# Copyright (C) 2012 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+import unittest
+import tempfile
+import subprocess
+import os
+from bb.fetch2 import URI
+from bb.fetch2 import FetchMethod
+import bb
+
+class URITest(unittest.TestCase):
+    test_uris = {
+        "http://www.google.com/index.html" : {
+            'uri': 'http://www.google.com/index.html',
+            'scheme': 'http',
+            'hostname': 'www.google.com',
+            'port': None,
+            'hostport': 'www.google.com',
+            'path': '/index.html',
+            'userinfo': '',
+            'username': '',
+            'password': '',
+            'params': {},
+            'query': {},
+            'relative': False
+        },
+        "http://www.google.com/index.html;param1=value1" : {
+            'uri': 'http://www.google.com/index.html;param1=value1',
+            'scheme': 'http',
+            'hostname': 'www.google.com',
+            'port': None,
+            'hostport': 'www.google.com',
+            'path': '/index.html',
+            'userinfo': '',
+            'username': '',
+            'password': '',
+            'params': {
+                'param1': 'value1'
+            },
+            'query': {},
+            'relative': False
+        },
+        "http://www.example.org/index.html?param1=value1" : {
+            'uri': 'http://www.example.org/index.html?param1=value1',
+            'scheme': 'http',
+            'hostname': 'www.example.org',
+            'port': None,
+            'hostport': 'www.example.org',
+            'path': '/index.html',
+            'userinfo': '',
+            'username': '',
+            'password': '',
+            'params': {},
+            'query': {
+                'param1': 'value1'
+            },
+            'relative': False
+        },
+        "http://www.example.org/index.html?qparam1=qvalue1;param2=value2" : {
+            'uri': 'http://www.example.org/index.html?qparam1=qvalue1;param2=value2',
+            'scheme': 'http',
+            'hostname': 'www.example.org',
+            'port': None,
+            'hostport': 'www.example.org',
+            'path': '/index.html',
+            'userinfo': '',
+            'username': '',
+            'password': '',
+            'params': {
+                'param2': 'value2'
+            },
+            'query': {
+                'qparam1': 'qvalue1'
+            },
+            'relative': False
+        },
+        "http://www.example.com:8080/index.html" : {
+            'uri': 'http://www.example.com:8080/index.html',
+            'scheme': 'http',
+            'hostname': 'www.example.com',
+            'port': 8080,
+            'hostport': 'www.example.com:8080',
+            'path': '/index.html',
+            'userinfo': '',
+            'username': '',
+            'password': '',
+            'params': {},
+            'query': {},
+            'relative': False
+        },
+        "cvs://anoncvs@cvs.handhelds.org/cvs;module=familiar/dist/ipkg" : {
+            'uri': 'cvs://anoncvs@cvs.handhelds.org/cvs;module=familiar/dist/ipkg',
+            'scheme': 'cvs',
+            'hostname': 'cvs.handhelds.org',
+            'port': None,
+            'hostport': 'cvs.handhelds.org',
+            'path': '/cvs',
+            'userinfo': 'anoncvs',
+            'username': 'anoncvs',
+            'password': '',
+            'params': {
+                'module': 'familiar/dist/ipkg'
+            },
+            'query': {},
+            'relative': False
+        },
+        "cvs://anoncvs:anonymous@cvs.handhelds.org/cvs;tag=V0-99-81;module=familiar/dist/ipkg": {
+            'uri': 'cvs://anoncvs:anonymous@cvs.handhelds.org/cvs;tag=V0-99-81;module=familiar/dist/ipkg',
+            'scheme': 'cvs',
+            'hostname': 'cvs.handhelds.org',
+            'port': None,
+            'hostport': 'cvs.handhelds.org',
+            'path': '/cvs',
+            'userinfo': 'anoncvs:anonymous',
+            'username': 'anoncvs',
+            'password': 'anonymous',
+            'params': {
+                'tag': 'V0-99-81',
+                'module': 'familiar/dist/ipkg'
+            },
+            'query': {},
+            'relative': False
+        },
+        "file://example.diff": { # NOTE: Not RFC compliant!
+            'uri': 'file:example.diff',
+            'scheme': 'file',
+            'hostname': '',
+            'port': None,
+            'hostport': '',
+            'path': 'example.diff',
+            'userinfo': '',
+            'username': '',
+            'password': '',
+            'params': {},
+            'query': {},
+            'relative': True
+        },
+        "file:example.diff": { # NOTE: RFC compliant version of the former
+            'uri': 'file:example.diff',
+            'scheme': 'file',
+            'hostname': '',
+            'port': None,
+            'hostport': '',
+            'path': 'example.diff',
+            'userinfo': '',
+            'userinfo': '',
+            'username': '',
+            'password': '',
+            'params': {},
+            'query': {},
+            'relative': True
+        },
+        "file:///tmp/example.diff": {
+            'uri': 'file:///tmp/example.diff',
+            'scheme': 'file',
+            'hostname': '',
+            'port': None,
+            'hostport': '',
+            'path': '/tmp/example.diff',
+            'userinfo': '',
+            'userinfo': '',
+            'username': '',
+            'password': '',
+            'params': {},
+            'query': {},
+            'relative': False
+        },
+        "git:///path/example.git": {
+            'uri': 'git:///path/example.git',
+            'scheme': 'git',
+            'hostname': '',
+            'port': None,
+            'hostport': '',
+            'path': '/path/example.git',
+            'userinfo': '',
+            'userinfo': '',
+            'username': '',
+            'password': '',
+            'params': {},
+            'query': {},
+            'relative': False
+        },
+        "git:path/example.git": {
+            'uri': 'git:path/example.git',
+            'scheme': 'git',
+            'hostname': '',
+            'port': None,
+            'hostport': '',
+            'path': 'path/example.git',
+            'userinfo': '',
+            'userinfo': '',
+            'username': '',
+            'password': '',
+            'params': {},
+            'query': {},
+            'relative': True
+        },
+        "git://example.net/path/example.git": {
+            'uri': 'git://example.net/path/example.git',
+            'scheme': 'git',
+            'hostname': 'example.net',
+            'port': None,
+            'hostport': 'example.net',
+            'path': '/path/example.git',
+            'userinfo': '',
+            'userinfo': '',
+            'username': '',
+            'password': '',
+            'params': {},
+            'query': {},
+            'relative': False
+        }
+    }
+
+    def test_uri(self):
+        for test_uri, ref in self.test_uris.items():
+            uri = URI(test_uri)
+
+            self.assertEqual(str(uri), ref['uri'])
+
+            # expected attributes
+            self.assertEqual(uri.scheme, ref['scheme'])
+
+            self.assertEqual(uri.userinfo, ref['userinfo'])
+            self.assertEqual(uri.username, ref['username'])
+            self.assertEqual(uri.password, ref['password'])
+
+            self.assertEqual(uri.hostname, ref['hostname'])
+            self.assertEqual(uri.port, ref['port'])
+            self.assertEqual(uri.hostport, ref['hostport'])
+
+            self.assertEqual(uri.path, ref['path'])
+            self.assertEqual(uri.params, ref['params'])
+
+            self.assertEqual(uri.relative, ref['relative'])
+
+    def test_dict(self):
+        for test in self.test_uris.values():
+            uri = URI()
+
+            self.assertEqual(uri.scheme, '')
+            self.assertEqual(uri.userinfo, '')
+            self.assertEqual(uri.username, '')
+            self.assertEqual(uri.password, '')
+            self.assertEqual(uri.hostname, '')
+            self.assertEqual(uri.port, None)
+            self.assertEqual(uri.path, '')
+            self.assertEqual(uri.params, {})
+
+
+            uri.scheme = test['scheme']
+            self.assertEqual(uri.scheme, test['scheme'])
+
+            uri.userinfo = test['userinfo']
+            self.assertEqual(uri.userinfo, test['userinfo'])
+            self.assertEqual(uri.username, test['username'])
+            self.assertEqual(uri.password, test['password'])
+
+            # make sure changing the values doesn't do anything unexpected
+            uri.username = 'changeme'
+            self.assertEqual(uri.username, 'changeme')
+            self.assertEqual(uri.password, test['password'])
+            uri.password = 'insecure'
+            self.assertEqual(uri.username, 'changeme')
+            self.assertEqual(uri.password, 'insecure')
+
+            # reset back after our trickery
+            uri.userinfo = test['userinfo']
+            self.assertEqual(uri.userinfo, test['userinfo'])
+            self.assertEqual(uri.username, test['username'])
+            self.assertEqual(uri.password, test['password'])
+
+            uri.hostname = test['hostname']
+            self.assertEqual(uri.hostname, test['hostname'])
+            self.assertEqual(uri.hostport, test['hostname'])
+
+            uri.port = test['port']
+            self.assertEqual(uri.port, test['port'])
+            self.assertEqual(uri.hostport, test['hostport'])
+
+            uri.path = test['path']
+            self.assertEqual(uri.path, test['path'])
+
+            uri.params = test['params']
+            self.assertEqual(uri.params, test['params'])
+
+            uri.query = test['query']
+            self.assertEqual(uri.query, test['query'])
+
+            self.assertEqual(str(uri), test['uri'])
+
+            uri.params = {}
+            self.assertEqual(uri.params, {})
+            self.assertEqual(str(uri), (str(uri).split(";"))[0])
+
+class FetcherTest(unittest.TestCase):
+
+    def setUp(self):
+        self.origdir = os.getcwd()
+        self.d = bb.data.init()
+        self.tempdir = tempfile.mkdtemp()
+        self.dldir = os.path.join(self.tempdir, "download")
+        os.mkdir(self.dldir)
+        self.d.setVar("DL_DIR", self.dldir)
+        self.unpackdir = os.path.join(self.tempdir, "unpacked")
+        os.mkdir(self.unpackdir)
+        persistdir = os.path.join(self.tempdir, "persistdata")
+        self.d.setVar("PERSISTENT_DIR", persistdir)
+
+    def tearDown(self):
+        os.chdir(self.origdir)
+        bb.utils.prunedir(self.tempdir)
+
+class MirrorUriTest(FetcherTest):
+
+    replaceuris = {
+        ("git://git.invalid.infradead.org/mtd-utils.git;tag=1234567890123456789012345678901234567890", "git://.*/.*", "http://somewhere.org/somedir/") 
+            : "http://somewhere.org/somedir/git2_git.invalid.infradead.org.mtd-utils.git.tar.gz",
+        ("git://git.invalid.infradead.org/mtd-utils.git;tag=1234567890123456789012345678901234567890", "git://.*/([^/]+/)*([^/]*)", "git://somewhere.org/somedir/\\2;protocol=http") 
+            : "git://somewhere.org/somedir/mtd-utils.git;tag=1234567890123456789012345678901234567890;protocol=http", 
+        ("git://git.invalid.infradead.org/foo/mtd-utils.git;tag=1234567890123456789012345678901234567890", "git://.*/([^/]+/)*([^/]*)", "git://somewhere.org/somedir/\\2;protocol=http") 
+            : "git://somewhere.org/somedir/mtd-utils.git;tag=1234567890123456789012345678901234567890;protocol=http", 
+        ("git://git.invalid.infradead.org/foo/mtd-utils.git;tag=1234567890123456789012345678901234567890", "git://.*/([^/]+/)*([^/]*)", "git://somewhere.org/\\2;protocol=http") 
+            : "git://somewhere.org/mtd-utils.git;tag=1234567890123456789012345678901234567890;protocol=http", 
+        ("git://someserver.org/bitbake;tag=1234567890123456789012345678901234567890", "git://someserver.org/bitbake", "git://git.openembedded.org/bitbake")
+            : "git://git.openembedded.org/bitbake;tag=1234567890123456789012345678901234567890",
+        ("file://sstate-xyz.tgz", "file://.*", "file:///somewhere/1234/sstate-cache") 
+            : "file:///somewhere/1234/sstate-cache/sstate-xyz.tgz",
+        ("file://sstate-xyz.tgz", "file://.*", "file:///somewhere/1234/sstate-cache/") 
+            : "file:///somewhere/1234/sstate-cache/sstate-xyz.tgz",
+        ("http://somewhere.org/somedir1/somedir2/somefile_1.2.3.tar.gz", "http://.*/.*", "http://somewhere2.org/somedir3") 
+            : "http://somewhere2.org/somedir3/somefile_1.2.3.tar.gz",
+        ("http://somewhere.org/somedir1/somefile_1.2.3.tar.gz", "http://somewhere.org/somedir1/somefile_1.2.3.tar.gz", "http://somewhere2.org/somedir3/somefile_1.2.3.tar.gz") 
+            : "http://somewhere2.org/somedir3/somefile_1.2.3.tar.gz",
+        ("http://www.apache.org/dist/subversion/subversion-1.7.1.tar.bz2", "http://www.apache.org/dist", "http://archive.apache.org/dist")
+            : "http://archive.apache.org/dist/subversion/subversion-1.7.1.tar.bz2",
+        ("http://www.apache.org/dist/subversion/subversion-1.7.1.tar.bz2", "http://.*/.*", "file:///somepath/downloads/")
+            : "file:///somepath/downloads/subversion-1.7.1.tar.bz2",
+        ("git://git.invalid.infradead.org/mtd-utils.git;tag=1234567890123456789012345678901234567890", "git://.*/.*", "git://somewhere.org/somedir/BASENAME;protocol=http") 
+            : "git://somewhere.org/somedir/mtd-utils.git;tag=1234567890123456789012345678901234567890;protocol=http", 
+        ("git://git.invalid.infradead.org/foo/mtd-utils.git;tag=1234567890123456789012345678901234567890", "git://.*/.*", "git://somewhere.org/somedir/BASENAME;protocol=http") 
+            : "git://somewhere.org/somedir/mtd-utils.git;tag=1234567890123456789012345678901234567890;protocol=http", 
+        ("git://git.invalid.infradead.org/foo/mtd-utils.git;tag=1234567890123456789012345678901234567890", "git://.*/.*", "git://somewhere.org/somedir/MIRRORNAME;protocol=http") 
+            : "git://somewhere.org/somedir/git.invalid.infradead.org.foo.mtd-utils.git;tag=1234567890123456789012345678901234567890;protocol=http", 
+
+        #Renaming files doesn't work
+        #("http://somewhere.org/somedir1/somefile_1.2.3.tar.gz", "http://somewhere.org/somedir1/somefile_1.2.3.tar.gz", "http://somewhere2.org/somedir3/somefile_2.3.4.tar.gz") : "http://somewhere2.org/somedir3/somefile_2.3.4.tar.gz"
+        #("file://sstate-xyz.tgz", "file://.*/.*", "file:///somewhere/1234/sstate-cache") : "file:///somewhere/1234/sstate-cache/sstate-xyz.tgz",
+    }
+
+    mirrorvar = "http://.*/.* file:///somepath/downloads/ \n" \
+                "git://someserver.org/bitbake git://git.openembedded.org/bitbake \n" \
+                "https://.*/.* file:///someotherpath/downloads/ \n" \
+                "http://.*/.* file:///someotherpath/downloads/ \n"
+
+    def test_urireplace(self):
+        for k, v in self.replaceuris.items():
+            ud = bb.fetch.FetchData(k[0], self.d)
+            ud.setup_localpath(self.d)
+            mirrors = bb.fetch2.mirror_from_string("%s %s" % (k[1], k[2]))
+            newuris, uds = bb.fetch2.build_mirroruris(ud, mirrors, self.d)
+            self.assertEqual([v], newuris)
+
+    def test_urilist1(self):
+        fetcher = bb.fetch.FetchData("http://downloads.yoctoproject.org/releases/bitbake/bitbake-1.0.tar.gz", self.d)
+        mirrors = bb.fetch2.mirror_from_string(self.mirrorvar)
+        uris, uds = bb.fetch2.build_mirroruris(fetcher, mirrors, self.d)
+        self.assertEqual(uris, ['file:///somepath/downloads/bitbake-1.0.tar.gz', 'file:///someotherpath/downloads/bitbake-1.0.tar.gz'])
+
+    def test_urilist2(self):
+        # Catch https:// -> files:// bug
+        fetcher = bb.fetch.FetchData("https://downloads.yoctoproject.org/releases/bitbake/bitbake-1.0.tar.gz", self.d)
+        mirrors = bb.fetch2.mirror_from_string(self.mirrorvar)
+        uris, uds = bb.fetch2.build_mirroruris(fetcher, mirrors, self.d)
+        self.assertEqual(uris, ['file:///someotherpath/downloads/bitbake-1.0.tar.gz'])
+
+    def test_mirror_of_mirror(self):
+        # Test if mirror of a mirror works
+        mirrorvar = self.mirrorvar + " http://.*/.* http://otherdownloads.yoctoproject.org/downloads/ \n"
+        mirrorvar = mirrorvar + " http://otherdownloads.yoctoproject.org/.* http://downloads2.yoctoproject.org/downloads/ \n"
+        fetcher = bb.fetch.FetchData("http://downloads.yoctoproject.org/releases/bitbake/bitbake-1.0.tar.gz", self.d)
+        mirrors = bb.fetch2.mirror_from_string(mirrorvar)
+        uris, uds = bb.fetch2.build_mirroruris(fetcher, mirrors, self.d)
+        self.assertEqual(uris, ['file:///somepath/downloads/bitbake-1.0.tar.gz', 
+                                'file:///someotherpath/downloads/bitbake-1.0.tar.gz', 
+                                'http://otherdownloads.yoctoproject.org/downloads/bitbake-1.0.tar.gz',
+                                'http://downloads2.yoctoproject.org/downloads/bitbake-1.0.tar.gz'])
+
+
+class FetcherLocalTest(FetcherTest):
+    def setUp(self):
+        def touch(fn):
+            with file(fn, 'a'):
+                os.utime(fn, None)
+
+        super(FetcherLocalTest, self).setUp()
+        self.localsrcdir = os.path.join(self.tempdir, 'localsrc')
+        os.makedirs(self.localsrcdir)
+        touch(os.path.join(self.localsrcdir, 'a'))
+        touch(os.path.join(self.localsrcdir, 'b'))
+        os.makedirs(os.path.join(self.localsrcdir, 'dir'))
+        touch(os.path.join(self.localsrcdir, 'dir', 'c'))
+        touch(os.path.join(self.localsrcdir, 'dir', 'd'))
+        os.makedirs(os.path.join(self.localsrcdir, 'dir', 'subdir'))
+        touch(os.path.join(self.localsrcdir, 'dir', 'subdir', 'e'))
+        self.d.setVar("FILESPATH", self.localsrcdir)
+
+    def fetchUnpack(self, uris):
+        fetcher = bb.fetch.Fetch(uris, self.d)
+        fetcher.download()
+        fetcher.unpack(self.unpackdir)
+        flst = []
+        for root, dirs, files in os.walk(self.unpackdir):
+            for f in files:
+                flst.append(os.path.relpath(os.path.join(root, f), self.unpackdir))
+        flst.sort()
+        return flst
+
+    def test_local(self):
+        tree = self.fetchUnpack(['file://a', 'file://dir/c'])
+        self.assertEqual(tree, ['a', 'dir/c'])
+
+    def test_local_wildcard(self):
+        tree = self.fetchUnpack(['file://a', 'file://dir/*'])
+        # FIXME: this is broken - it should return ['a', 'dir/c', 'dir/d', 'dir/subdir/e']
+        # see https://bugzilla.yoctoproject.org/show_bug.cgi?id=6128
+        self.assertEqual(tree, ['a', 'b', 'dir/c', 'dir/d', 'dir/subdir/e'])
+
+    def test_local_dir(self):
+        tree = self.fetchUnpack(['file://a', 'file://dir'])
+        self.assertEqual(tree, ['a', 'dir/c', 'dir/d', 'dir/subdir/e'])
+
+    def test_local_subdir(self):
+        tree = self.fetchUnpack(['file://dir/subdir'])
+        # FIXME: this is broken - it should return ['dir/subdir/e']
+        # see https://bugzilla.yoctoproject.org/show_bug.cgi?id=6129
+        self.assertEqual(tree, ['subdir/e'])
+
+    def test_local_subdir_file(self):
+        tree = self.fetchUnpack(['file://dir/subdir/e'])
+        self.assertEqual(tree, ['dir/subdir/e'])
+
+    def test_local_subdirparam(self):
+        tree = self.fetchUnpack(['file://a;subdir=bar'])
+        self.assertEqual(tree, ['bar/a'])
+
+    def test_local_deepsubdirparam(self):
+        tree = self.fetchUnpack(['file://dir/subdir/e;subdir=bar'])
+        self.assertEqual(tree, ['bar/dir/subdir/e'])
+
+class FetcherNetworkTest(FetcherTest):
+
+    if os.environ.get("BB_SKIP_NETTESTS") == "yes":
+        print("Unset BB_SKIP_NETTESTS to run network tests")
+    else:
+        def test_fetch(self):
+            fetcher = bb.fetch.Fetch(["http://downloads.yoctoproject.org/releases/bitbake/bitbake-1.0.tar.gz", "http://downloads.yoctoproject.org/releases/bitbake/bitbake-1.1.tar.gz"], self.d)
+            fetcher.download()
+            self.assertEqual(os.path.getsize(self.dldir + "/bitbake-1.0.tar.gz"), 57749)
+            self.assertEqual(os.path.getsize(self.dldir + "/bitbake-1.1.tar.gz"), 57892)
+            self.d.setVar("BB_NO_NETWORK", "1")
+            fetcher = bb.fetch.Fetch(["http://downloads.yoctoproject.org/releases/bitbake/bitbake-1.0.tar.gz", "http://downloads.yoctoproject.org/releases/bitbake/bitbake-1.1.tar.gz"], self.d)
+            fetcher.download()
+            fetcher.unpack(self.unpackdir)
+            self.assertEqual(len(os.listdir(self.unpackdir + "/bitbake-1.0/")), 9)
+            self.assertEqual(len(os.listdir(self.unpackdir + "/bitbake-1.1/")), 9)
+
+        def test_fetch_mirror(self):
+            self.d.setVar("MIRRORS", "http://.*/.* http://downloads.yoctoproject.org/releases/bitbake")
+            fetcher = bb.fetch.Fetch(["http://invalid.yoctoproject.org/releases/bitbake/bitbake-1.0.tar.gz"], self.d)
+            fetcher.download()
+            self.assertEqual(os.path.getsize(self.dldir + "/bitbake-1.0.tar.gz"), 57749)
+
+        def test_fetch_mirror_of_mirror(self):
+            self.d.setVar("MIRRORS", "http://.*/.* http://invalid2.yoctoproject.org/ \n http://invalid2.yoctoproject.org/.* http://downloads.yoctoproject.org/releases/bitbake")
+            fetcher = bb.fetch.Fetch(["http://invalid.yoctoproject.org/releases/bitbake/bitbake-1.0.tar.gz"], self.d)
+            fetcher.download()
+            self.assertEqual(os.path.getsize(self.dldir + "/bitbake-1.0.tar.gz"), 57749)
+
+        def test_fetch_file_mirror_of_mirror(self):
+            self.d.setVar("MIRRORS", "http://.*/.* file:///some1where/ \n file:///some1where/.* file://some2where/ \n file://some2where/.* http://downloads.yoctoproject.org/releases/bitbake")
+            fetcher = bb.fetch.Fetch(["http://invalid.yoctoproject.org/releases/bitbake/bitbake-1.0.tar.gz"], self.d)
+            os.mkdir(self.dldir + "/some2where")
+            fetcher.download()
+            self.assertEqual(os.path.getsize(self.dldir + "/bitbake-1.0.tar.gz"), 57749)
+
+        def test_fetch_premirror(self):
+            self.d.setVar("PREMIRRORS", "http://.*/.* http://downloads.yoctoproject.org/releases/bitbake")
+            fetcher = bb.fetch.Fetch(["http://invalid.yoctoproject.org/releases/bitbake/bitbake-1.0.tar.gz"], self.d)
+            fetcher.download()
+            self.assertEqual(os.path.getsize(self.dldir + "/bitbake-1.0.tar.gz"), 57749)
+
+        def gitfetcher(self, url1, url2):
+            def checkrevision(self, fetcher):
+                fetcher.unpack(self.unpackdir)
+                revision = bb.process.run("git rev-parse HEAD", shell=True, cwd=self.unpackdir + "/git")[0].strip()
+                self.assertEqual(revision, "270a05b0b4ba0959fe0624d2a4885d7b70426da5")
+
+            self.d.setVar("BB_GENERATE_MIRROR_TARBALLS", "1")
+            self.d.setVar("SRCREV", "270a05b0b4ba0959fe0624d2a4885d7b70426da5")
+            fetcher = bb.fetch.Fetch([url1], self.d)
+            fetcher.download()
+            checkrevision(self, fetcher)
+            # Wipe out the dldir clone and the unpacked source, turn off the network and check mirror tarball works
+            bb.utils.prunedir(self.dldir + "/git2/")
+            bb.utils.prunedir(self.unpackdir)
+            self.d.setVar("BB_NO_NETWORK", "1")
+            fetcher = bb.fetch.Fetch([url2], self.d)
+            fetcher.download()
+            checkrevision(self, fetcher)
+
+        def test_gitfetch(self):
+            url1 = url2 = "git://git.openembedded.org/bitbake"
+            self.gitfetcher(url1, url2)
+
+        def test_gitfetch_goodsrcrev(self):
+            # SRCREV is set but matches rev= parameter
+            url1 = url2 = "git://git.openembedded.org/bitbake;rev=270a05b0b4ba0959fe0624d2a4885d7b70426da5"
+            self.gitfetcher(url1, url2)
+
+        def test_gitfetch_badsrcrev(self):
+            # SRCREV is set but does not match rev= parameter
+            url1 = url2 = "git://git.openembedded.org/bitbake;rev=dead05b0b4ba0959fe0624d2a4885d7b70426da5"
+            self.assertRaises(bb.fetch.FetchError, self.gitfetcher, url1, url2)
+
+        def test_gitfetch_tagandrev(self):
+            # SRCREV is set but does not match rev= parameter
+            url1 = url2 = "git://git.openembedded.org/bitbake;rev=270a05b0b4ba0959fe0624d2a4885d7b70426da5;tag=270a05b0b4ba0959fe0624d2a4885d7b70426da5"
+            self.assertRaises(bb.fetch.FetchError, self.gitfetcher, url1, url2)
+
+        def test_gitfetch_premirror(self):
+            url1 = "git://git.openembedded.org/bitbake"
+            url2 = "git://someserver.org/bitbake"
+            self.d.setVar("PREMIRRORS", "git://someserver.org/bitbake git://git.openembedded.org/bitbake \n")
+            self.gitfetcher(url1, url2)
+
+        def test_gitfetch_premirror2(self):
+            url1 = url2 = "git://someserver.org/bitbake"
+            self.d.setVar("PREMIRRORS", "git://someserver.org/bitbake git://git.openembedded.org/bitbake \n")
+            self.gitfetcher(url1, url2)
+
+        def test_gitfetch_premirror3(self):
+            realurl = "git://git.openembedded.org/bitbake"
+            dummyurl = "git://someserver.org/bitbake"
+            self.sourcedir = self.unpackdir.replace("unpacked", "sourcemirror.git")
+            os.chdir(self.tempdir)
+            bb.process.run("git clone %s %s 2> /dev/null" % (realurl, self.sourcedir), shell=True)
+            self.d.setVar("PREMIRRORS", "%s git://%s;protocol=file \n" % (dummyurl, self.sourcedir))
+            self.gitfetcher(dummyurl, dummyurl)
+
+        def test_git_submodule(self):
+            fetcher = bb.fetch.Fetch(["gitsm://git.yoctoproject.org/git-submodule-test;rev=f12e57f2edf0aa534cf1616fa983d165a92b0842"], self.d)
+            fetcher.download()
+            # Previous cwd has been deleted
+            os.chdir(os.path.dirname(self.unpackdir))
+            fetcher.unpack(self.unpackdir)
+
+        def test_trusted_network(self):
+            # Ensure trusted_network returns False when the host IS in the list.
+            url = "git://Someserver.org/foo;rev=1"
+            self.d.setVar("BB_ALLOWED_NETWORKS", "server1.org someserver.org server2.org server3.org")
+            self.assertTrue(bb.fetch.trusted_network(self.d, url))
+
+        def test_wild_trusted_network(self):
+            # Ensure trusted_network returns true when the *.host IS in the list.
+            url = "git://Someserver.org/foo;rev=1"
+            self.d.setVar("BB_ALLOWED_NETWORKS", "server1.org *.someserver.org server2.org server3.org")
+            self.assertTrue(bb.fetch.trusted_network(self.d, url))
+
+        def test_prefix_wild_trusted_network(self):
+            # Ensure trusted_network returns true when the prefix matches *.host.
+            url = "git://git.Someserver.org/foo;rev=1"
+            self.d.setVar("BB_ALLOWED_NETWORKS", "server1.org *.someserver.org server2.org server3.org")
+            self.assertTrue(bb.fetch.trusted_network(self.d, url))
+
+        def test_two_prefix_wild_trusted_network(self):
+            # Ensure trusted_network returns true when the prefix matches *.host.
+            url = "git://something.git.Someserver.org/foo;rev=1"
+            self.d.setVar("BB_ALLOWED_NETWORKS", "server1.org *.someserver.org server2.org server3.org")
+            self.assertTrue(bb.fetch.trusted_network(self.d, url))
+
+        def test_untrusted_network(self):
+            # Ensure trusted_network returns False when the host is NOT in the list.
+            url = "git://someserver.org/foo;rev=1"
+            self.d.setVar("BB_ALLOWED_NETWORKS", "server1.org server2.org server3.org")
+            self.assertFalse(bb.fetch.trusted_network(self.d, url))
+
+        def test_wild_untrusted_network(self):
+            # Ensure trusted_network returns False when the host is NOT in the list.
+            url = "git://*.someserver.org/foo;rev=1"
+            self.d.setVar("BB_ALLOWED_NETWORKS", "server1.org server2.org server3.org")
+            self.assertFalse(bb.fetch.trusted_network(self.d, url))
+
+
+class URLHandle(unittest.TestCase):
+
+    datatable = {
+       "http://www.google.com/index.html" : ('http', 'www.google.com', '/index.html', '', '', {}),
+       "cvs://anoncvs@cvs.handhelds.org/cvs;module=familiar/dist/ipkg" : ('cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', '', {'module': 'familiar/dist/ipkg'}),
+       "cvs://anoncvs:anonymous@cvs.handhelds.org/cvs;tag=V0-99-81;module=familiar/dist/ipkg" : ('cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', 'anonymous', {'tag': 'V0-99-81', 'module': 'familiar/dist/ipkg'}),
+       "git://git.openembedded.org/bitbake;branch=@foo" : ('git', 'git.openembedded.org', '/bitbake', '', '', {'branch': '@foo'})
+    }
+
+    def test_decodeurl(self):
+        for k, v in self.datatable.items():
+            result = bb.fetch.decodeurl(k)
+            self.assertEqual(result, v)
+
+    def test_encodeurl(self):
+        for k, v in self.datatable.items():
+            result = bb.fetch.encodeurl(v)
+            self.assertEqual(result, k)
+
+class FetchLatestVersionTest(FetcherTest):
+
+    test_git_uris = {
+        # version pattern "X.Y.Z"
+        ("mx-1.0", "git://github.com/clutter-project/mx.git;branch=mx-1.4", "9b1db6b8060bd00b121a692f942404a24ae2960f", "")
+            : "1.99.4",
+        # version pattern "vX.Y"
+        ("mtd-utils", "git://git.infradead.org/mtd-utils.git", "ca39eb1d98e736109c64ff9c1aa2a6ecca222d8f", "")
+            : "1.5.0",
+        # version pattern "pkg_name-X.Y"
+        ("presentproto", "git://anongit.freedesktop.org/git/xorg/proto/presentproto", "24f3a56e541b0a9e6c6ee76081f441221a120ef9", "")
+            : "1.0",
+        # version pattern "pkg_name-vX.Y.Z"
+        ("dtc", "git://git.qemu.org/dtc.git", "65cc4d2748a2c2e6f27f1cf39e07a5dbabd80ebf", "")
+            : "1.4.0",
+        # combination version pattern
+        ("sysprof", "git://git.gnome.org/sysprof", "cd44ee6644c3641507fb53b8a2a69137f2971219", "")
+            : "1.2.0",
+        ("u-boot-mkimage", "git://git.denx.de/u-boot.git;branch=master;protocol=git", "62c175fbb8a0f9a926c88294ea9f7e88eb898f6c", "")
+            : "2014.01",
+        # version pattern "yyyymmdd"
+        ("mobile-broadband-provider-info", "git://git.gnome.org/mobile-broadband-provider-info", "4ed19e11c2975105b71b956440acdb25d46a347d", "")
+            : "20120614",
+        # packages with a valid GITTAGREGEX
+        ("xf86-video-omap", "git://anongit.freedesktop.org/xorg/driver/xf86-video-omap", "ae0394e687f1a77e966cf72f895da91840dffb8f", "(?P<pver>(\d+\.(\d\.?)*))")
+            : "0.4.3",
+        ("build-appliance-image", "git://git.yoctoproject.org/poky", "b37dd451a52622d5b570183a81583cc34c2ff555", "(?P<pver>(([0-9][\.|_]?)+[0-9]))")
+            : "11.0.0",
+        ("chkconfig-alternatives-native", "git://github.com/kergoth/chkconfig;branch=sysroot", "cd437ecbd8986c894442f8fce1e0061e20f04dee", "chkconfig\-(?P<pver>((\d+[\.\-_]*)+))")
+            : "1.3.59",
+        ("remake", "git://github.com/rocky/remake.git", "f05508e521987c8494c92d9c2871aec46307d51d", "(?P<pver>(\d+\.(\d+\.)*\d*(\+dbg\d+(\.\d+)*)*))")
+            : "3.82+dbg0.9",
+    }
+
+    test_wget_uris = {
+        # packages with versions inside directory name
+        ("util-linux", "http://kernel.org/pub/linux/utils/util-linux/v2.23/util-linux-2.24.2.tar.bz2", "", "")
+            : "2.24.2",
+        ("enchant", "http://www.abisource.com/downloads/enchant/1.6.0/enchant-1.6.0.tar.gz", "", "")
+            : "1.6.0",
+        ("cmake", "http://www.cmake.org/files/v2.8/cmake-2.8.12.1.tar.gz", "", "")
+            : "2.8.12.1",
+        # packages with versions only in current directory
+        ("eglic", "http://downloads.yoctoproject.org/releases/eglibc/eglibc-2.18-svnr23787.tar.bz2", "", "")
+            : "2.19",
+        ("gnu-config", "http://downloads.yoctoproject.org/releases/gnu-config/gnu-config-20120814.tar.bz2", "", "")
+            : "20120814",
+        # packages with "99" in the name of possible version
+        ("pulseaudio", "http://freedesktop.org/software/pulseaudio/releases/pulseaudio-4.0.tar.xz", "", "")
+            : "5.0",
+        ("xserver-xorg", "http://xorg.freedesktop.org/releases/individual/xserver/xorg-server-1.15.1.tar.bz2", "", "")
+            : "1.15.1",
+        # packages with valid REGEX_URI and REGEX
+        ("cups", "http://www.cups.org/software/1.7.2/cups-1.7.2-source.tar.bz2", "http://www.cups.org/software.php", "(?P<name>cups\-)(?P<pver>((\d+[\.\-_]*)+))\-source\.tar\.gz")
+            : "2.0.0",
+        ("db", "http://download.oracle.com/berkeley-db/db-5.3.21.tar.gz", "http://www.oracle.com/technetwork/products/berkeleydb/downloads/index-082944.html", "http://download.oracle.com/otn/berkeley-db/(?P<name>db-)(?P<pver>((\d+[\.\-_]*)+))\.tar\.gz")
+            : "6.1.19",
+    }
+    if os.environ.get("BB_SKIP_NETTESTS") == "yes":
+        print("Unset BB_SKIP_NETTESTS to run network tests")
+    else:
+        def test_git_latest_versionstring(self):
+            for k, v in self.test_git_uris.items():
+                self.d.setVar("PN", k[0])
+                self.d.setVar("SRCREV", k[2])
+                self.d.setVar("GITTAGREGEX", k[3])
+                ud = bb.fetch2.FetchData(k[1], self.d)
+                pupver= ud.method.latest_versionstring(ud, self.d)
+                verstring = pupver[0]
+                r = bb.utils.vercmp_string(v, verstring)
+                self.assertTrue(r == -1 or r == 0, msg="Package %s, version: %s <= %s" % (k[0], v, verstring))
+
+        def test_wget_latest_versionstring(self):
+            for k, v in self.test_wget_uris.items():
+                self.d.setVar("PN", k[0])
+                self.d.setVar("REGEX_URI", k[2])
+                self.d.setVar("REGEX", k[3])
+                ud = bb.fetch2.FetchData(k[1], self.d)
+                pupver = ud.method.latest_versionstring(ud, self.d)
+                verstring = pupver[0]
+                r = bb.utils.vercmp_string(v, verstring)
+                self.assertTrue(r == -1 or r == 0, msg="Package %s, version: %s <= %s" % (k[0], v, verstring))
+
+
+class FetchCheckStatusTest(FetcherTest):
+    test_wget_uris = ["http://www.cups.org/software/1.7.2/cups-1.7.2-source.tar.bz2",
+                      "http://www.cups.org/software/ipptool/ipptool-20130731-linux-ubuntu-i686.tar.gz",
+                      "http://www.cups.org/",
+                      "http://downloads.yoctoproject.org/releases/sato/sato-engine-0.1.tar.gz",
+                      "http://downloads.yoctoproject.org/releases/sato/sato-engine-0.2.tar.gz",
+                      "http://downloads.yoctoproject.org/releases/sato/sato-engine-0.3.tar.gz",
+                      "https://yoctoproject.org/",
+                      "https://yoctoproject.org/documentation",
+                      "http://downloads.yoctoproject.org/releases/opkg/opkg-0.1.7.tar.gz",
+                      "http://downloads.yoctoproject.org/releases/opkg/opkg-0.3.0.tar.gz",
+                      "ftp://ftp.gnu.org/gnu/autoconf/autoconf-2.60.tar.gz",
+                      "ftp://ftp.gnu.org/gnu/chess/gnuchess-5.08.tar.gz",
+                      "ftp://ftp.gnu.org/gnu/gmp/gmp-4.0.tar.gz",
+                      ]
+
+    if os.environ.get("BB_SKIP_NETTESTS") == "yes":
+        print("Unset BB_SKIP_NETTESTS to run network tests")
+    else:
+
+        def test_wget_checkstatus(self):
+            fetch = bb.fetch2.Fetch(self.test_wget_uris, self.d)
+            for u in self.test_wget_uris:
+                ud = fetch.ud[u]
+                m = ud.method
+                ret = m.checkstatus(fetch, ud, self.d)
+                self.assertTrue(ret, msg="URI %s, can't check status" % (u))
+
+
+        def test_wget_checkstatus_connection_cache(self):
+            from bb.fetch2 import FetchConnectionCache
+
+            connection_cache = FetchConnectionCache()
+            fetch = bb.fetch2.Fetch(self.test_wget_uris, self.d,
+                        connection_cache = connection_cache)
+
+            for u in self.test_wget_uris:
+                ud = fetch.ud[u]
+                m = ud.method
+                ret = m.checkstatus(fetch, ud, self.d)
+                self.assertTrue(ret, msg="URI %s, can't check status" % (u))
+
+            connection_cache.close_connections()
diff --git a/bitbake/lib/bb/tests/parse.py b/bitbake/lib/bb/tests/parse.py
new file mode 100644
index 0000000..6beb76a
--- /dev/null
+++ b/bitbake/lib/bb/tests/parse.py
@@ -0,0 +1,147 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Test for lib/bb/parse/
+#
+# Copyright (C) 2015 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+import unittest
+import tempfile
+import logging
+import bb
+import os
+
+logger = logging.getLogger('BitBake.TestParse')
+
+import bb.parse
+import bb.data
+import bb.siggen
+
+class ParseTest(unittest.TestCase):
+
+    testfile = """
+A = "1"
+B = "2"
+do_install() {
+	echo "hello"
+}
+
+C = "3"
+"""
+
+    def setUp(self):
+        self.d = bb.data.init()
+        bb.parse.siggen = bb.siggen.init(self.d)
+
+    def parsehelper(self, content, suffix = ".bb"):
+
+        f = tempfile.NamedTemporaryFile(suffix = suffix)
+        f.write(content)
+        f.flush()
+        os.chdir(os.path.dirname(f.name))
+        return f
+
+    def test_parse_simple(self):
+        f = self.parsehelper(self.testfile)
+        d = bb.parse.handle(f.name, self.d)['']
+        self.assertEqual(d.getVar("A", True), "1")
+        self.assertEqual(d.getVar("B", True), "2")
+        self.assertEqual(d.getVar("C", True), "3")
+
+    def test_parse_incomplete_function(self):
+        testfileB = self.testfile.replace("}", "")
+        f = self.parsehelper(testfileB)
+        with self.assertRaises(bb.parse.ParseError):
+            d = bb.parse.handle(f.name, self.d)['']
+
+    overridetest = """
+RRECOMMENDS_${PN} = "a"
+RRECOMMENDS_${PN}_libc = "b"
+OVERRIDES = "libc:${PN}"
+PN = "gtk+"
+"""
+
+    def test_parse_overrides(self):
+        f = self.parsehelper(self.overridetest)
+        d = bb.parse.handle(f.name, self.d)['']
+        self.assertEqual(d.getVar("RRECOMMENDS", True), "b")
+        bb.data.expandKeys(d)
+        self.assertEqual(d.getVar("RRECOMMENDS", True), "b")
+        d.setVar("RRECOMMENDS_gtk+", "c")
+        self.assertEqual(d.getVar("RRECOMMENDS", True), "c")
+
+    overridetest2 = """
+EXTRA_OECONF = ""
+EXTRA_OECONF_class-target = "b"
+EXTRA_OECONF_append = " c"
+"""
+
+    def test_parse_overrides(self):
+        f = self.parsehelper(self.overridetest2)
+        d = bb.parse.handle(f.name, self.d)['']
+        d.appendVar("EXTRA_OECONF", " d")
+        d.setVar("OVERRIDES", "class-target")
+        self.assertEqual(d.getVar("EXTRA_OECONF", True), "b c d")
+
+    overridetest3 = """
+DESCRIPTION = "A"
+DESCRIPTION_${PN}-dev = "${DESCRIPTION} B"
+PN = "bc"
+"""
+
+    def test_parse_combinations(self):
+        f = self.parsehelper(self.overridetest3)
+        d = bb.parse.handle(f.name, self.d)['']
+        bb.data.expandKeys(d)
+        self.assertEqual(d.getVar("DESCRIPTION_bc-dev", True), "A B")
+        d.setVar("DESCRIPTION", "E")
+        d.setVar("DESCRIPTION_bc-dev", "C D")
+        d.setVar("OVERRIDES", "bc-dev")
+        self.assertEqual(d.getVar("DESCRIPTION", True), "C D")
+
+
+    classextend = """
+VAR_var_override1 = "B"
+EXTRA = ":override1"
+OVERRIDES = "nothing${EXTRA}"
+
+BBCLASSEXTEND = "###CLASS###"
+"""
+    classextend_bbclass = """
+EXTRA = ""
+python () {
+    d.renameVar("VAR_var", "VAR_var2")
+}
+"""
+
+    #
+    # Test based upon a real world data corruption issue. One
+    # data store changing a variable poked through into a different data
+    # store. This test case replicates that issue where the value 'B' would 
+    # become unset/disappear.
+    #
+    def test_parse_classextend_contamination(self):
+        cls = self.parsehelper(self.classextend_bbclass, suffix=".bbclass")
+        #clsname = os.path.basename(cls.name).replace(".bbclass", "")
+        self.classextend = self.classextend.replace("###CLASS###", cls.name)
+        f = self.parsehelper(self.classextend)
+        alldata = bb.parse.handle(f.name, self.d)
+        d1 = alldata['']
+        d2 = alldata[cls.name]
+        self.assertEqual(d1.getVar("VAR_var", True), "B")
+        self.assertEqual(d2.getVar("VAR_var", True), None)
+
diff --git a/bitbake/lib/bb/tests/utils.py b/bitbake/lib/bb/tests/utils.py
new file mode 100644
index 0000000..9171509
--- /dev/null
+++ b/bitbake/lib/bb/tests/utils.py
@@ -0,0 +1,378 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Tests for utils.py
+#
+# Copyright (C) 2012 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+import unittest
+import bb
+import os
+import tempfile
+
+class VerCmpString(unittest.TestCase):
+
+    def test_vercmpstring(self):
+        result = bb.utils.vercmp_string('1', '2')
+        self.assertTrue(result < 0)
+        result = bb.utils.vercmp_string('2', '1')
+        self.assertTrue(result > 0)
+        result = bb.utils.vercmp_string('1', '1.0')
+        self.assertTrue(result < 0)
+        result = bb.utils.vercmp_string('1', '1.1')
+        self.assertTrue(result < 0)
+        result = bb.utils.vercmp_string('1.1', '1_p2')
+        self.assertTrue(result < 0)
+        result = bb.utils.vercmp_string('1.0', '1.0+1.1-beta1')
+        self.assertTrue(result < 0)
+        result = bb.utils.vercmp_string('1.1', '1.0+1.1-beta1')
+        self.assertTrue(result > 0)
+
+    def test_explode_dep_versions(self):
+        correctresult = {"foo" : ["= 1.10"]}
+        result = bb.utils.explode_dep_versions2("foo (= 1.10)")
+        self.assertEqual(result, correctresult)
+        result = bb.utils.explode_dep_versions2("foo (=1.10)")
+        self.assertEqual(result, correctresult)
+        result = bb.utils.explode_dep_versions2("foo ( = 1.10)")
+        self.assertEqual(result, correctresult)
+        result = bb.utils.explode_dep_versions2("foo ( =1.10)")
+        self.assertEqual(result, correctresult)
+        result = bb.utils.explode_dep_versions2("foo ( = 1.10 )")
+        self.assertEqual(result, correctresult)
+        result = bb.utils.explode_dep_versions2("foo ( =1.10 )")
+        self.assertEqual(result, correctresult)
+
+    def test_vercmp_string_op(self):
+        compareops = [('1', '1', '=', True),
+                      ('1', '1', '==', True),
+                      ('1', '1', '!=', False),
+                      ('1', '1', '>', False),
+                      ('1', '1', '<', False),
+                      ('1', '1', '>=', True),
+                      ('1', '1', '<=', True),
+                      ('1', '0', '=', False),
+                      ('1', '0', '==', False),
+                      ('1', '0', '!=', True),
+                      ('1', '0', '>', True),
+                      ('1', '0', '<', False),
+                      ('1', '0', '>>', True),
+                      ('1', '0', '<<', False),
+                      ('1', '0', '>=', True),
+                      ('1', '0', '<=', False),
+                      ('0', '1', '=', False),
+                      ('0', '1', '==', False),
+                      ('0', '1', '!=', True),
+                      ('0', '1', '>', False),
+                      ('0', '1', '<', True),
+                      ('0', '1', '>>', False),
+                      ('0', '1', '<<', True),
+                      ('0', '1', '>=', False),
+                      ('0', '1', '<=', True)]
+
+        for arg1, arg2, op, correctresult in compareops:
+            result = bb.utils.vercmp_string_op(arg1, arg2, op)
+            self.assertEqual(result, correctresult, 'vercmp_string_op("%s", "%s", "%s") != %s' % (arg1, arg2, op, correctresult))
+
+        # Check that clearly invalid operator raises an exception
+        self.assertRaises(bb.utils.VersionStringException, bb.utils.vercmp_string_op, '0', '0', '$')
+
+
+class Path(unittest.TestCase):
+    def test_unsafe_delete_path(self):
+        checkitems = [('/', True),
+                      ('//', True),
+                      ('///', True),
+                      (os.getcwd().count(os.sep) * ('..' + os.sep), True),
+                      (os.environ.get('HOME', '/home/test'), True),
+                      ('/home/someone', True),
+                      ('/home/other/', True),
+                      ('/home/other/subdir', False),
+                      ('', False)]
+        for arg1, correctresult in checkitems:
+            result = bb.utils._check_unsafe_delete_path(arg1)
+            self.assertEqual(result, correctresult, '_check_unsafe_delete_path("%s") != %s' % (arg1, correctresult))
+
+
+class EditMetadataFile(unittest.TestCase):
+    _origfile = """
+# A comment
+HELLO = "oldvalue"
+
+THIS = "that"
+
+# Another comment
+NOCHANGE = "samevalue"
+OTHER = 'anothervalue'
+
+MULTILINE = "a1 \\
+             a2 \\
+             a3"
+
+MULTILINE2 := " \\
+               b1 \\
+               b2 \\
+               b3 \\
+               "
+
+
+MULTILINE3 = " \\
+              c1 \\
+              c2 \\
+              c3 \\
+"
+
+do_functionname() {
+    command1 ${VAL1} ${VAL2}
+    command2 ${VAL3} ${VAL4}
+}
+"""
+    def _testeditfile(self, varvalues, compareto, dummyvars=None):
+        if dummyvars is None:
+            dummyvars = []
+        with tempfile.NamedTemporaryFile('w', delete=False) as tf:
+            tf.write(self._origfile)
+            tf.close()
+            try:
+                varcalls = []
+                def handle_file(varname, origvalue, op, newlines):
+                    self.assertIn(varname, varvalues, 'Callback called for variable %s not in the list!' % varname)
+                    self.assertNotIn(varname, dummyvars, 'Callback called for variable %s in dummy list!' % varname)
+                    varcalls.append(varname)
+                    return varvalues[varname]
+
+                bb.utils.edit_metadata_file(tf.name, varvalues.keys(), handle_file)
+                with open(tf.name) as f:
+                    modfile = f.readlines()
+                # Ensure the output matches the expected output
+                self.assertEqual(compareto.splitlines(True), modfile)
+                # Ensure the callback function was called for every variable we asked for
+                # (plus allow testing behaviour when a requested variable is not present)
+                self.assertEqual(sorted(varvalues.keys()), sorted(varcalls + dummyvars))
+            finally:
+                os.remove(tf.name)
+
+
+    def test_edit_metadata_file_nochange(self):
+        # Test file doesn't get modified with nothing to do
+        self._testeditfile({}, self._origfile)
+        # Test file doesn't get modified with only dummy variables
+        self._testeditfile({'DUMMY1': ('should_not_set', None, 0, True),
+                        'DUMMY2': ('should_not_set_again', None, 0, True)}, self._origfile, dummyvars=['DUMMY1', 'DUMMY2'])
+        # Test file doesn't get modified with some the same values
+        self._testeditfile({'THIS': ('that', None, 0, True),
+                        'OTHER': ('anothervalue', None, 0, True),
+                        'MULTILINE3': ('               c1               c2               c3', None, 4, False)}, self._origfile)
+
+    def test_edit_metadata_file_1(self):
+
+        newfile1 = """
+# A comment
+HELLO = "newvalue"
+
+THIS = "that"
+
+# Another comment
+NOCHANGE = "samevalue"
+OTHER = 'anothervalue'
+
+MULTILINE = "a1 \\
+             a2 \\
+             a3"
+
+MULTILINE2 := " \\
+               b1 \\
+               b2 \\
+               b3 \\
+               "
+
+
+MULTILINE3 = " \\
+              c1 \\
+              c2 \\
+              c3 \\
+"
+
+do_functionname() {
+    command1 ${VAL1} ${VAL2}
+    command2 ${VAL3} ${VAL4}
+}
+"""
+        self._testeditfile({'HELLO': ('newvalue', None, 4, True)}, newfile1)
+
+
+    def test_edit_metadata_file_2(self):
+
+        newfile2 = """
+# A comment
+HELLO = "oldvalue"
+
+THIS = "that"
+
+# Another comment
+NOCHANGE = "samevalue"
+OTHER = 'anothervalue'
+
+MULTILINE = " \\
+    d1 \\
+    d2 \\
+    d3 \\
+    "
+
+MULTILINE2 := " \\
+               b1 \\
+               b2 \\
+               b3 \\
+               "
+
+
+MULTILINE3 = "nowsingle"
+
+do_functionname() {
+    command1 ${VAL1} ${VAL2}
+    command2 ${VAL3} ${VAL4}
+}
+"""
+        self._testeditfile({'MULTILINE': (['d1','d2','d3'], None, 4, False),
+                        'MULTILINE3': ('nowsingle', None, 4, True),
+                        'NOTPRESENT': (['a', 'b'], None, 4, False)}, newfile2, dummyvars=['NOTPRESENT'])
+
+
+    def test_edit_metadata_file_3(self):
+
+        newfile3 = """
+# A comment
+HELLO = "oldvalue"
+
+# Another comment
+NOCHANGE = "samevalue"
+OTHER = "yetanothervalue"
+
+MULTILINE = "e1 \\
+             e2 \\
+             e3 \\
+             "
+
+MULTILINE2 := "f1 \\
+\tf2 \\
+\t"
+
+
+MULTILINE3 = " \\
+              c1 \\
+              c2 \\
+              c3 \\
+"
+
+do_functionname() {
+    othercommand_one a b c
+    othercommand_two d e f
+}
+"""
+
+        self._testeditfile({'do_functionname()': (['othercommand_one a b c', 'othercommand_two d e f'], None, 4, False),
+                        'MULTILINE2': (['f1', 'f2'], None, '\t', True),
+                        'MULTILINE': (['e1', 'e2', 'e3'], None, -1, True),
+                        'THIS': (None, None, 0, False),
+                        'OTHER': ('yetanothervalue', None, 0, True)}, newfile3)
+
+
+    def test_edit_metadata_file_4(self):
+
+        newfile4 = """
+# A comment
+HELLO = "oldvalue"
+
+THIS = "that"
+
+# Another comment
+OTHER = 'anothervalue'
+
+MULTILINE = "a1 \\
+             a2 \\
+             a3"
+
+MULTILINE2 := " \\
+               b1 \\
+               b2 \\
+               b3 \\
+               "
+
+
+"""
+
+        self._testeditfile({'NOCHANGE': (None, None, 0, False),
+                        'MULTILINE3': (None, None, 0, False),
+                        'THIS': ('that', None, 0, False),
+                        'do_functionname()': (None, None, 0, False)}, newfile4)
+
+
+    def test_edit_metadata(self):
+        newfile5 = """
+# A comment
+HELLO = "hithere"
+
+# A new comment
+THIS += "that"
+
+# Another comment
+NOCHANGE = "samevalue"
+OTHER = 'anothervalue'
+
+MULTILINE = "a1 \\
+             a2 \\
+             a3"
+
+MULTILINE2 := " \\
+               b1 \\
+               b2 \\
+               b3 \\
+               "
+
+
+MULTILINE3 = " \\
+              c1 \\
+              c2 \\
+              c3 \\
+"
+
+NEWVAR = "value"
+
+do_functionname() {
+    command1 ${VAL1} ${VAL2}
+    command2 ${VAL3} ${VAL4}
+}
+"""
+
+
+        def handle_var(varname, origvalue, op, newlines):
+            if varname == 'THIS':
+                newlines.append('# A new comment\n')
+            elif varname == 'do_functionname()':
+                newlines.append('NEWVAR = "value"\n')
+                newlines.append('\n')
+            valueitem = varvalues.get(varname, None)
+            if valueitem:
+                return valueitem
+            else:
+                return (origvalue, op, 0, True)
+
+        varvalues = {'HELLO': ('hithere', None, 0, True), 'THIS': ('that', '+=', 0, True)}
+        varlist = ['HELLO', 'THIS', 'do_functionname()']
+        (updated, newlines) = bb.utils.edit_metadata(self._origfile.splitlines(True), varlist, handle_var)
+        self.assertTrue(updated, 'List should be updated but isn\'t')
+        self.assertEqual(newlines, newfile5.splitlines(True))
diff --git a/bitbake/lib/bb/tinfoil.py b/bitbake/lib/bb/tinfoil.py
new file mode 100644
index 0000000..1ea46d8
--- /dev/null
+++ b/bitbake/lib/bb/tinfoil.py
@@ -0,0 +1,104 @@
+# tinfoil: a simple wrapper around cooker for bitbake-based command-line utilities
+#
+# Copyright (C) 2012 Intel Corporation
+# Copyright (C) 2011 Mentor Graphics Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import logging
+import warnings
+import os
+import sys
+
+import bb.cache
+import bb.cooker
+import bb.providers
+import bb.utils
+from bb.cooker import state, BBCooker, CookerFeatures
+from bb.cookerdata import CookerConfiguration, ConfigParameters
+import bb.fetch2
+
+class Tinfoil:
+    def __init__(self, output=sys.stdout, tracking=False):
+        # Needed to avoid deprecation warnings with python 2.6
+        warnings.filterwarnings("ignore", category=DeprecationWarning)
+
+        # Set up logging
+        self.logger = logging.getLogger('BitBake')
+        console = logging.StreamHandler(output)
+        bb.msg.addDefaultlogFilter(console)
+        format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
+        if output.isatty():
+            format.enable_color()
+        console.setFormatter(format)
+        self.logger.addHandler(console)
+
+        self.config = CookerConfiguration()
+        configparams = TinfoilConfigParameters(parse_only=True)
+        self.config.setConfigParameters(configparams)
+        self.config.setServerRegIdleCallback(self.register_idle_function)
+        features = []
+        if tracking:
+            features.append(CookerFeatures.BASEDATASTORE_TRACKING)
+        self.cooker = BBCooker(self.config, features)
+        self.config_data = self.cooker.data
+        bb.providers.logger.setLevel(logging.ERROR)
+        self.cooker_data = None
+
+    def register_idle_function(self, function, data):
+        pass
+
+    def parseRecipes(self):
+        sys.stderr.write("Parsing recipes..")
+        self.logger.setLevel(logging.WARNING)
+
+        try:
+            while self.cooker.state in (state.initial, state.parsing):
+                self.cooker.updateCache()
+        except KeyboardInterrupt:
+            self.cooker.shutdown()
+            self.cooker.updateCache()
+            sys.exit(2)
+
+        self.logger.setLevel(logging.INFO)
+        sys.stderr.write("done.\n")
+
+        self.cooker_data = self.cooker.recipecache
+
+    def prepare(self, config_only = False):
+        if not self.cooker_data:
+            if config_only:
+                self.cooker.parseConfiguration()
+                self.cooker_data = self.cooker.recipecache
+            else:
+                self.parseRecipes()
+
+    def shutdown(self):
+        self.cooker.shutdown(force=True)
+        self.cooker.post_serve()
+        self.cooker.unlockBitbake()
+
+class TinfoilConfigParameters(ConfigParameters):
+
+    def __init__(self, **options):
+        self.initial_options = options
+        super(TinfoilConfigParameters, self).__init__()
+
+    def parseCommandLine(self, argv=sys.argv):
+        class DummyOptions:
+            def __init__(self, initial_options):
+                for key, val in initial_options.items():
+                    setattr(self, key, val)
+
+        return DummyOptions(self.initial_options), None
diff --git a/bitbake/lib/bb/ui/__init__.py b/bitbake/lib/bb/ui/__init__.py
new file mode 100644
index 0000000..a4805ed
--- /dev/null
+++ b/bitbake/lib/bb/ui/__init__.py
@@ -0,0 +1,17 @@
+#
+# BitBake UI Implementation
+#
+# Copyright (C) 2006-2007 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
new file mode 100644
index 0000000..2d1ed51
--- /dev/null
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -0,0 +1,1339 @@
+#
+# BitBake ToasterUI Implementation
+#
+# Copyright (C) 2013        Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import sys
+import bb
+import re
+import os
+
+os.environ["DJANGO_SETTINGS_MODULE"] = "toaster.toastermain.settings"
+
+
+from django.utils import timezone
+
+
+def _configure_toaster():
+    """ Add toaster to sys path for importing modules
+    """
+    sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'toaster'))
+_configure_toaster()
+
+from toaster.orm.models import Build, Task, Recipe, Layer_Version, Layer, Target, LogMessage, HelpText
+from toaster.orm.models import Target_Image_File, BuildArtifact
+from toaster.orm.models import Variable, VariableHistory
+from toaster.orm.models import Package, Package_File, Target_Installed_Package, Target_File
+from toaster.orm.models import Task_Dependency, Package_Dependency
+from toaster.orm.models import Recipe_Dependency
+
+from toaster.orm.models import Project
+from bldcontrol.models import BuildEnvironment, BuildRequest
+
+from bb.msg import BBLogFormatter as formatter
+from django.db import models
+from pprint import pformat
+import logging
+
+from django.db import transaction, connection
+
+# pylint: disable=invalid-name
+# the logger name is standard throughout BitBake
+logger = logging.getLogger("ToasterLogger")
+
+
+class NotExisting(Exception):
+    pass
+
+class ORMWrapper(object):
+    """ This class creates the dictionaries needed to store information in the database
+        following the format defined by the Django models. It is also used to save this
+        information in the database.
+    """
+
+    def __init__(self):
+        self.layer_version_objects = []
+        self.task_objects = {}
+        self.recipe_objects = {}
+
+    @staticmethod
+    def _build_key(**kwargs):
+        key = "0"
+        for k in sorted(kwargs.keys()):
+            if isinstance(kwargs[k], models.Model):
+                key += "-%d" % kwargs[k].id
+            else:
+                key += "-%s" % str(kwargs[k])
+        return key
+
+
+    def _cached_get_or_create(self, clazz, **kwargs):
+        """ This is a memory-cached get_or_create. We assume that the objects will not be created in the
+            database through any other means.
+        """
+
+        assert issubclass(clazz, models.Model), "_cached_get_or_create needs to get the class as first argument"
+
+        key = ORMWrapper._build_key(**kwargs)
+        dictname = "objects_%s" % clazz.__name__
+        if not dictname in vars(self).keys():
+            vars(self)[dictname] = {}
+
+        created = False
+        if not key in vars(self)[dictname].keys():
+            vars(self)[dictname][key] = clazz.objects.create(**kwargs)
+            created = True
+
+        return (vars(self)[dictname][key], created)
+
+
+    def _cached_get(self, clazz, **kwargs):
+        """ This is a memory-cached get. We assume that the objects will not change  in the database between gets.
+        """
+        assert issubclass(clazz, models.Model), "_cached_get needs to get the class as first argument"
+
+        key = ORMWrapper._build_key(**kwargs)
+        dictname = "objects_%s" % clazz.__name__
+
+        if not dictname in vars(self).keys():
+            vars(self)[dictname] = {}
+
+        if not key in vars(self)[dictname].keys():
+            vars(self)[dictname][key] = clazz.objects.get(**kwargs)
+
+        return vars(self)[dictname][key]
+
+    # pylint: disable=no-self-use
+    # we disable detection of no self use in functions because the methods actually work on the object
+    # even if they don't touch self anywhere
+
+    # pylint: disable=bad-continuation
+    # we do not follow the python conventions for continuation indentation due to long lines here
+
+    def create_build_object(self, build_info, brbe, project_id):
+        assert 'machine' in build_info
+        assert 'distro' in build_info
+        assert 'distro_version' in build_info
+        assert 'started_on' in build_info
+        assert 'cooker_log_path' in build_info
+        assert 'build_name' in build_info
+        assert 'bitbake_version' in build_info
+
+        prj = None
+        buildrequest = None
+        if brbe is not None:            # this build was triggered by a request from a user
+            logger.debug(1, "buildinfohelper: brbe is %s" % brbe)
+            br, _ = brbe.split(":")
+            buildrequest = BuildRequest.objects.get(pk = br)
+            prj = buildrequest.project
+
+        elif project_id is not None:    # this build was triggered by an external system for a specific project
+            logger.debug(1, "buildinfohelper: project is %s" % prj)
+            prj = Project.objects.get(pk = project_id)
+
+        else:                           # this build was triggered by a legacy system, or command line interactive mode
+            prj = Project.objects.get_default_project()
+            logger.debug(1, "buildinfohelper: project is not specified, defaulting to %s" % prj)
+
+
+        if buildrequest is not None:
+            build = buildrequest.build
+            logger.info("Updating existing build, with %s", build_info)
+            build.project = prj
+            build.machine=build_info['machine']
+            build.distro=build_info['distro']
+            build.distro_version=build_info['distro_version']
+            build.cooker_log_path=build_info['cooker_log_path']
+            build.build_name=build_info['build_name']
+            build.bitbake_version=build_info['bitbake_version']
+            build.save()
+
+            Target.objects.filter(build = build).delete()
+
+        else:
+            build = Build.objects.create(
+                                    project = prj,
+                                    machine=build_info['machine'],
+                                    distro=build_info['distro'],
+                                    distro_version=build_info['distro_version'],
+                                    started_on=build_info['started_on'],
+                                    completed_on=build_info['started_on'],
+                                    cooker_log_path=build_info['cooker_log_path'],
+                                    build_name=build_info['build_name'],
+                                    bitbake_version=build_info['bitbake_version'])
+
+        logger.debug(1, "buildinfohelper: build is created %s" % build)
+
+        if buildrequest is not None:
+            buildrequest.build = build
+            buildrequest.save()
+
+        return build
+
+    def create_target_objects(self, target_info):
+        assert 'build' in target_info
+        assert 'targets' in target_info
+
+        targets = []
+        for tgt_name in target_info['targets']:
+            tgt_object = Target.objects.create( build = target_info['build'],
+                                    target = tgt_name,
+                                    is_image = False,
+                                    )
+            targets.append(tgt_object)
+        return targets
+
+    def update_build_object(self, build, errors, warnings, taskfailures):
+        assert isinstance(build,Build)
+        assert isinstance(errors, int)
+        assert isinstance(warnings, int)
+
+        outcome = Build.SUCCEEDED
+        if errors or taskfailures:
+            outcome = Build.FAILED
+
+        build.completed_on = timezone.now()
+        build.outcome = outcome
+        build.save()
+
+    def update_target_set_license_manifest(self, target, license_manifest_path):
+        target.license_manifest_path = license_manifest_path
+        target.save()
+
+    def get_update_task_object(self, task_information, must_exist = False):
+        assert 'build' in task_information
+        assert 'recipe' in task_information
+        assert 'task_name' in task_information
+
+        # we use must_exist info for database look-up optimization
+        task_object, created = self._cached_get_or_create(Task,
+                        build=task_information['build'],
+                        recipe=task_information['recipe'],
+                        task_name=task_information['task_name']
+                        )
+        if created and must_exist:
+            task_information['debug'] = "build id %d, recipe id %d" % (task_information['build'].pk, task_information['recipe'].pk)
+            raise NotExisting("Task object created when expected to exist", task_information)
+
+        object_changed = False
+        for v in vars(task_object):
+            if v in task_information.keys():
+                if vars(task_object)[v] != task_information[v]:
+                    vars(task_object)[v] = task_information[v]
+                    object_changed = True
+
+        # update setscene-related information if the task has a setscene
+        if task_object.outcome == Task.OUTCOME_COVERED and 1 == task_object.get_related_setscene().count():
+            task_object.outcome = Task.OUTCOME_CACHED
+            object_changed = True
+
+            outcome_task_setscene = Task.objects.get(task_executed=True, build = task_object.build,
+                                    recipe = task_object.recipe, task_name=task_object.task_name+"_setscene").outcome
+            if outcome_task_setscene == Task.OUTCOME_SUCCESS:
+                task_object.sstate_result = Task.SSTATE_RESTORED
+                object_changed = True
+            elif outcome_task_setscene == Task.OUTCOME_FAILED:
+                task_object.sstate_result = Task.SSTATE_FAILED
+                object_changed = True
+
+        # mark down duration if we have a start time and a current time
+        if 'start_time' in task_information.keys() and 'end_time' in task_information.keys():
+            duration = task_information['end_time'] - task_information['start_time']
+            task_object.elapsed_time = duration
+            object_changed = True
+            del task_information['start_time']
+            del task_information['end_time']
+
+        if object_changed:
+            task_object.save()
+        return task_object
+
+
+    def get_update_recipe_object(self, recipe_information, must_exist = False):
+        assert 'layer_version' in recipe_information
+        assert 'file_path' in recipe_information
+        assert 'pathflags' in recipe_information
+
+        assert not recipe_information['file_path'].startswith("/")      # we should have layer-relative paths at all times
+
+        recipe_object, created = self._cached_get_or_create(Recipe, layer_version=recipe_information['layer_version'],
+                                     file_path=recipe_information['file_path'], pathflags = recipe_information['pathflags'])
+        if created and must_exist:
+            raise NotExisting("Recipe object created when expected to exist", recipe_information)
+
+        object_changed = False
+        for v in vars(recipe_object):
+            if v in recipe_information.keys():
+                object_changed = True
+                vars(recipe_object)[v] = recipe_information[v]
+
+        if object_changed:
+            recipe_object.save()
+
+        return recipe_object
+
+    def get_update_layer_version_object(self, build_obj, layer_obj, layer_version_information):
+        assert isinstance(build_obj, Build)
+        assert isinstance(layer_obj, Layer)
+        assert 'branch' in layer_version_information
+        assert 'commit' in layer_version_information
+        assert 'priority' in layer_version_information
+        assert 'local_path' in layer_version_information
+
+        layer_version_object, _ = Layer_Version.objects.get_or_create(
+                                    build = build_obj,
+                                    layer = layer_obj,
+                                    branch = layer_version_information['branch'],
+                                    commit = layer_version_information['commit'],
+                                    priority = layer_version_information['priority'],
+                                    local_path = layer_version_information['local_path'],
+                                    )
+
+        self.layer_version_objects.append(layer_version_object)
+
+        return layer_version_object
+
+    def get_update_layer_object(self, layer_information, brbe):
+        assert 'name' in layer_information
+        assert 'layer_index_url' in layer_information
+
+        if brbe is None:
+            layer_object, _ = Layer.objects.get_or_create(
+                                name=layer_information['name'],
+                                layer_index_url=layer_information['layer_index_url'])
+            return layer_object
+        else:
+            # we are under managed mode; we must match the layer used in the Project Layer
+            br_id, be_id = brbe.split(":")
+
+            # find layer by checkout path;
+            from bldcontrol import bbcontroller
+            bc = bbcontroller.getBuildEnvironmentController(pk = be_id)
+
+            # we might have a race condition here, as the project layers may change between the build trigger and the actual build execution
+            # but we can only match on the layer name, so the worst thing can happen is a mis-identification of the layer, not a total failure
+
+            # note that this is different
+            buildrequest = BuildRequest.objects.get(pk = br_id)
+            for brl in buildrequest.brlayer_set.all():
+                localdirname = os.path.join(bc.getGitCloneDirectory(brl.giturl, brl.commit), brl.dirpath)
+                # we get a relative path, unless running in HEAD mode where the path is absolute
+                if not localdirname.startswith("/"):
+                    localdirname = os.path.join(bc.be.sourcedir, localdirname)
+                #logger.debug(1, "Localdirname %s lcal_path %s" % (localdirname, layer_information['local_path']))
+                if localdirname.startswith(layer_information['local_path']):
+                    # we matched the BRLayer, but we need the layer_version that generated this BR; reverse of the Project.schedule_build()
+                    #logger.debug(1, "Matched %s to BRlayer %s" % (pformat(layer_information["local_path"]), localdirname))
+                    for pl in buildrequest.project.projectlayer_set.filter(layercommit__layer__name = brl.name):
+                        if pl.layercommit.layer.vcs_url == brl.giturl :
+                            layer = pl.layercommit.layer
+                            layer.save()
+                            return layer
+
+            raise NotExisting("Unidentified layer %s" % pformat(layer_information))
+
+
+    def save_target_file_information(self, build_obj, target_obj, filedata):
+        assert isinstance(build_obj, Build)
+        assert isinstance(target_obj, Target)
+        dirs = filedata['dirs']
+        files = filedata['files']
+        syms = filedata['syms']
+
+        # we insert directories, ordered by name depth
+        for d in sorted(dirs, key=lambda x:len(x[-1].split("/"))):
+            (user, group, size) = d[1:4]
+            permission = d[0][1:]
+            path = d[4].lstrip(".")
+            if len(path) == 0:
+                # we create the root directory as a special case
+                path = "/"
+                tf_obj = Target_File.objects.create(
+                        target = target_obj,
+                        path = path,
+                        size = size,
+                        inodetype = Target_File.ITYPE_DIRECTORY,
+                        permission = permission,
+                        owner = user,
+                        group = group,
+                        )
+                tf_obj.directory = tf_obj
+                tf_obj.save()
+                continue
+            parent_path = "/".join(path.split("/")[:len(path.split("/")) - 1])
+            if len(parent_path) == 0:
+                parent_path = "/"
+            parent_obj = self._cached_get(Target_File, target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY)
+            tf_obj = Target_File.objects.create(
+                        target = target_obj,
+                        path = path,
+                        size = size,
+                        inodetype = Target_File.ITYPE_DIRECTORY,
+                        permission = permission,
+                        owner = user,
+                        group = group,
+                        directory = parent_obj)
+
+
+        # we insert files
+        for d in files:
+            (user, group, size) = d[1:4]
+            permission = d[0][1:]
+            path = d[4].lstrip(".")
+            parent_path = "/".join(path.split("/")[:len(path.split("/")) - 1])
+            inodetype = Target_File.ITYPE_REGULAR
+            if d[0].startswith('b'):
+                inodetype = Target_File.ITYPE_BLOCK
+            if d[0].startswith('c'):
+                inodetype = Target_File.ITYPE_CHARACTER
+            if d[0].startswith('p'):
+                inodetype = Target_File.ITYPE_FIFO
+
+            tf_obj = Target_File.objects.create(
+                        target = target_obj,
+                        path = path,
+                        size = size,
+                        inodetype = inodetype,
+                        permission = permission,
+                        owner = user,
+                        group = group)
+            parent_obj = self._cached_get(Target_File, target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY)
+            tf_obj.directory = parent_obj
+            tf_obj.save()
+
+        # we insert symlinks
+        for d in syms:
+            (user, group, size) = d[1:4]
+            permission = d[0][1:]
+            path = d[4].lstrip(".")
+            filetarget_path = d[6]
+
+            parent_path = "/".join(path.split("/")[:len(path.split("/")) - 1])
+            if not filetarget_path.startswith("/"):
+                # we have a relative path, get a normalized absolute one
+                filetarget_path = parent_path + "/" + filetarget_path
+                fcp = filetarget_path.split("/")
+                fcpl = []
+                for i in fcp:
+                    if i == "..":
+                        fcpl.pop()
+                    else:
+                        fcpl.append(i)
+                filetarget_path = "/".join(fcpl)
+
+            try:
+                filetarget_obj = Target_File.objects.get(target = target_obj, path = filetarget_path)
+            except Target_File.DoesNotExist:
+                # we might have an invalid link; no way to detect this. just set it to None
+                filetarget_obj = None
+
+            parent_obj = Target_File.objects.get(target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY)
+
+            tf_obj = Target_File.objects.create(
+                        target = target_obj,
+                        path = path,
+                        size = size,
+                        inodetype = Target_File.ITYPE_SYMLINK,
+                        permission = permission,
+                        owner = user,
+                        group = group,
+                        directory = parent_obj,
+                        sym_target = filetarget_obj)
+
+
+    def save_target_package_information(self, build_obj, target_obj, packagedict, pkgpnmap, recipes):
+        assert isinstance(build_obj, Build)
+        assert isinstance(target_obj, Target)
+
+        errormsg = ""
+        for p in packagedict:
+            searchname = p
+            if 'OPKGN' in pkgpnmap[p].keys():
+                searchname = pkgpnmap[p]['OPKGN']
+
+            packagedict[p]['object'], created = Package.objects.get_or_create( build = build_obj, name = searchname )
+            if created or packagedict[p]['object'].size == -1:    # save the data anyway we can, not just if it was not created here; bug [YOCTO #6887]
+                # fill in everything we can from the runtime-reverse package data
+                try:
+                    packagedict[p]['object'].recipe = recipes[pkgpnmap[p]['PN']]
+                    packagedict[p]['object'].version = pkgpnmap[p]['PV']
+                    packagedict[p]['object'].installed_name = p
+                    packagedict[p]['object'].revision = pkgpnmap[p]['PR']
+                    packagedict[p]['object'].license = pkgpnmap[p]['LICENSE']
+                    packagedict[p]['object'].section = pkgpnmap[p]['SECTION']
+                    packagedict[p]['object'].summary = pkgpnmap[p]['SUMMARY']
+                    packagedict[p]['object'].description = pkgpnmap[p]['DESCRIPTION']
+                    packagedict[p]['object'].size = int(pkgpnmap[p]['PKGSIZE'])
+
+                # no files recorded for this package, so save files info
+                    packagefile_objects = []
+                    for targetpath in pkgpnmap[p]['FILES_INFO']:
+                        targetfilesize = pkgpnmap[p]['FILES_INFO'][targetpath]
+                        packagefile_objects.append(Package_File( package = packagedict[p]['object'],
+                            path = targetpath,
+                            size = targetfilesize))
+                    if len(packagefile_objects):
+                        Package_File.objects.bulk_create(packagefile_objects)
+                except KeyError as e:
+                    errormsg += "  stpi: Key error, package %s key %s \n" % ( p, e )
+
+            # save disk installed size
+            packagedict[p]['object'].installed_size = packagedict[p]['size']
+            packagedict[p]['object'].save()
+
+            Target_Installed_Package.objects.create(target = target_obj, package = packagedict[p]['object'])
+
+        packagedeps_objs = []
+        for p in packagedict:
+            for (px,deptype) in packagedict[p]['depends']:
+                if deptype == 'depends':
+                    tdeptype = Package_Dependency.TYPE_TRDEPENDS
+                elif deptype == 'recommends':
+                    tdeptype = Package_Dependency.TYPE_TRECOMMENDS
+
+                packagedeps_objs.append(Package_Dependency( package = packagedict[p]['object'],
+                                        depends_on = packagedict[px]['object'],
+                                        dep_type = tdeptype,
+                                        target = target_obj))
+
+        if len(packagedeps_objs) > 0:
+            Package_Dependency.objects.bulk_create(packagedeps_objs)
+
+        if len(errormsg) > 0:
+            logger.warn("buildinfohelper: target_package_info could not identify recipes: \n%s", errormsg)
+
+    def save_target_image_file_information(self, target_obj, file_name, file_size):
+        Target_Image_File.objects.create( target = target_obj,
+                            file_name = file_name,
+                            file_size = file_size)
+
+    def save_artifact_information(self, build_obj, file_name, file_size):
+        # we skip the image files from other builds
+        if Target_Image_File.objects.filter(file_name = file_name).count() > 0:
+            return
+
+        # do not update artifacts found in other builds
+        if BuildArtifact.objects.filter(file_name = file_name).count() > 0:
+            return
+
+        BuildArtifact.objects.create(build = build_obj, file_name = file_name, file_size = file_size)
+
+    def create_logmessage(self, log_information):
+        assert 'build' in log_information
+        assert 'level' in log_information
+        assert 'message' in log_information
+
+        log_object = LogMessage.objects.create(
+                        build = log_information['build'],
+                        level = log_information['level'],
+                        message = log_information['message'])
+
+        for v in vars(log_object):
+            if v in log_information.keys():
+                vars(log_object)[v] = log_information[v]
+
+        return log_object.save()
+
+
+    def save_build_package_information(self, build_obj, package_info, recipes):
+        assert isinstance(build_obj, Build)
+
+        # create and save the object
+        pname = package_info['PKG']
+        if 'OPKGN' in package_info.keys():
+            pname = package_info['OPKGN']
+
+        bp_object, _ = Package.objects.get_or_create( build = build_obj,
+                                       name = pname )
+
+        bp_object.installed_name = package_info['PKG']
+        bp_object.recipe = recipes[package_info['PN']]
+        bp_object.version = package_info['PKGV']
+        bp_object.revision = package_info['PKGR']
+        bp_object.summary = package_info['SUMMARY']
+        bp_object.description = package_info['DESCRIPTION']
+        bp_object.size = int(package_info['PKGSIZE'])
+        bp_object.section = package_info['SECTION']
+        bp_object.license = package_info['LICENSE']
+        bp_object.save()
+
+        # save any attached file information
+        packagefile_objects = []
+        for path in package_info['FILES_INFO']:
+            packagefile_objects.append(Package_File( package = bp_object,
+                                        path = path,
+                                        size = package_info['FILES_INFO'][path] ))
+        if len(packagefile_objects):
+            Package_File.objects.bulk_create(packagefile_objects)
+
+        def _po_byname(p):
+            pkg, created = Package.objects.get_or_create(build = build_obj, name = p)
+            if created:
+                pkg.size = -1
+                pkg.save()
+            return pkg
+
+        packagedeps_objs = []
+        # save soft dependency information
+        if 'RDEPENDS' in package_info and package_info['RDEPENDS']:
+            for p in bb.utils.explode_deps(package_info['RDEPENDS']):
+                packagedeps_objs.append(Package_Dependency(  package = bp_object,
+                    depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RDEPENDS))
+        if 'RPROVIDES' in package_info and package_info['RPROVIDES']:
+            for p in bb.utils.explode_deps(package_info['RPROVIDES']):
+                packagedeps_objs.append(Package_Dependency(  package = bp_object,
+                    depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RPROVIDES))
+        if 'RRECOMMENDS' in package_info and package_info['RRECOMMENDS']:
+            for p in bb.utils.explode_deps(package_info['RRECOMMENDS']):
+                packagedeps_objs.append(Package_Dependency(  package = bp_object,
+                    depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RRECOMMENDS))
+        if 'RSUGGESTS' in package_info and package_info['RSUGGESTS']:
+            for p in bb.utils.explode_deps(package_info['RSUGGESTS']):
+                packagedeps_objs.append(Package_Dependency(  package = bp_object,
+                    depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RSUGGESTS))
+        if 'RREPLACES' in package_info and package_info['RREPLACES']:
+            for p in bb.utils.explode_deps(package_info['RREPLACES']):
+                packagedeps_objs.append(Package_Dependency(  package = bp_object,
+                    depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RREPLACES))
+        if 'RCONFLICTS' in package_info and package_info['RCONFLICTS']:
+            for p in bb.utils.explode_deps(package_info['RCONFLICTS']):
+                packagedeps_objs.append(Package_Dependency(  package = bp_object,
+                    depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RCONFLICTS))
+
+        if len(packagedeps_objs) > 0:
+            Package_Dependency.objects.bulk_create(packagedeps_objs)
+
+        return bp_object
+
+    def save_build_variables(self, build_obj, vardump):
+        assert isinstance(build_obj, Build)
+
+        helptext_objects = []
+        for k in vardump:
+            desc = vardump[k]['doc']
+            if desc is None:
+                var_words = [word for word in k.split('_')]
+                root_var = "_".join([word for word in var_words if word.isupper()])
+                if root_var and root_var != k and root_var in vardump:
+                    desc = vardump[root_var]['doc']
+            if desc is None:
+                desc = ''
+            if len(desc):
+                helptext_objects.append(HelpText(build=build_obj,
+                    area=HelpText.VARIABLE,
+                    key=k,
+                    text=desc))
+            if not bool(vardump[k]['func']):
+                value = vardump[k]['v']
+                if value is None:
+                    value = ''
+                variable_obj = Variable.objects.create( build = build_obj,
+                    variable_name = k,
+                    variable_value = value,
+                    description = desc)
+
+                varhist_objects = []
+                for vh in vardump[k]['history']:
+                    if not 'documentation.conf' in vh['file']:
+                        varhist_objects.append(VariableHistory( variable = variable_obj,
+                                file_name = vh['file'],
+                                line_number = vh['line'],
+                                operation = vh['op']))
+                if len(varhist_objects):
+                    VariableHistory.objects.bulk_create(varhist_objects)
+
+        HelpText.objects.bulk_create(helptext_objects)
+
+
+class MockEvent(object):
+    """ This object is used to create event, for which normal event-processing methods can
+        be used, out of data that is not coming via an actual event
+    """
+    def __init__(self):
+        self.msg = None
+        self.levelno = None
+        self.taskname = None
+        self.taskhash = None
+        self.pathname = None
+        self.lineno = None
+
+
+class BuildInfoHelper(object):
+    """ This class gathers the build information from the server and sends it
+        towards the ORM wrapper for storing in the database
+        It is instantiated once per build
+        Keeps in memory all data that needs matching before writing it to the database
+    """
+
+    # pylint: disable=protected-access
+    # the code will look into the protected variables of the event; no easy way around this
+    # pylint: disable=bad-continuation
+    # we do not follow the python conventions for continuation indentation due to long lines here
+
+    def __init__(self, server, has_build_history = False):
+        self.internal_state = {}
+        self.internal_state['taskdata'] = {}
+        self.task_order = 0
+        self.autocommit_step = 1
+        self.server = server
+        # we use manual transactions if the database doesn't autocommit on us
+        if not connection.features.autocommits_when_autocommit_is_off:
+            transaction.set_autocommit(False)
+        self.orm_wrapper = ORMWrapper()
+        self.has_build_history = has_build_history
+        self.tmp_dir = self.server.runCommand(["getVariable", "TMPDIR"])[0]
+        self.brbe    = self.server.runCommand(["getVariable", "TOASTER_BRBE"])[0]
+        self.project = self.server.runCommand(["getVariable", "TOASTER_PROJECT"])[0]
+        logger.debug(1, "buildinfohelper: Build info helper inited %s" % vars(self))
+
+
+    ###################
+    ## methods to convert event/external info into objects that the ORM layer uses
+
+
+    def _get_build_information(self):
+        build_info = {}
+        # Generate an identifier for each new build
+
+        build_info['machine'] = self.server.runCommand(["getVariable", "MACHINE"])[0]
+        build_info['distro'] = self.server.runCommand(["getVariable", "DISTRO"])[0]
+        build_info['distro_version'] = self.server.runCommand(["getVariable", "DISTRO_VERSION"])[0]
+        build_info['started_on'] = timezone.now()
+        build_info['completed_on'] = timezone.now()
+        build_info['cooker_log_path'] = self.server.runCommand(["getVariable", "BB_CONSOLELOG"])[0]
+        build_info['build_name'] = self.server.runCommand(["getVariable", "BUILDNAME"])[0]
+        build_info['bitbake_version'] = self.server.runCommand(["getVariable", "BB_VERSION"])[0]
+
+        return build_info
+
+    def _get_task_information(self, event, recipe):
+        assert 'taskname' in vars(event)
+
+        task_information = {}
+        task_information['build'] = self.internal_state['build']
+        task_information['outcome'] = Task.OUTCOME_NA
+        task_information['recipe'] = recipe
+        task_information['task_name'] = event.taskname
+        try:
+            # some tasks don't come with a hash. and that's ok
+            task_information['sstate_checksum'] = event.taskhash
+        except AttributeError:
+            pass
+        return task_information
+
+    def _get_layer_version_for_path(self, path):
+        assert path.startswith("/")
+        assert 'build' in self.internal_state
+
+        if self.brbe is None:
+            def _slkey_interactive(layer_version):
+                assert isinstance(layer_version, Layer_Version)
+                return len(layer_version.local_path)
+
+            # Heuristics: we always match recipe to the deepest layer path in the discovered layers
+            for lvo in sorted(self.orm_wrapper.layer_version_objects, reverse=True, key=_slkey_interactive):
+                # we can match to the recipe file path
+                if path.startswith(lvo.local_path):
+                    return lvo
+
+        else:
+            br_id, be_id = self.brbe.split(":")
+            from bldcontrol.bbcontroller import getBuildEnvironmentController
+            bc = getBuildEnvironmentController(pk = be_id)
+
+            def _slkey_managed(layer_version):
+                return len(bc.getGitCloneDirectory(layer_version.giturl, layer_version.commit) + layer_version.dirpath)
+
+            # Heuristics: we match the path to where the layers have been checked out
+            for brl in sorted(BuildRequest.objects.get(pk = br_id).brlayer_set.all(), reverse = True, key = _slkey_managed):
+                localdirname = os.path.join(bc.getGitCloneDirectory(brl.giturl, brl.commit), brl.dirpath)
+                # we get a relative path, unless running in HEAD mode where the path is absolute
+                if not localdirname.startswith("/"):
+                    localdirname = os.path.join(bc.be.sourcedir, localdirname)
+                if path.startswith(localdirname):
+                    #logger.warn("-- managed: matched path %s with layer %s " % (path, localdirname))
+                    # we matched the BRLayer, but we need the layer_version that generated this br
+                    for lvo in self.orm_wrapper.layer_version_objects:
+                        if brl.name == lvo.layer.name:
+                            return lvo
+
+        #if we get here, we didn't read layers correctly; dump whatever information we have on the error log
+        logger.warn("Could not match layer version for recipe path %s : %s", path, self.orm_wrapper.layer_version_objects)
+
+        #mockup the new layer
+        unknown_layer, _ = Layer.objects.get_or_create(name="__FIXME__unidentified_layer", layer_index_url="")
+        unknown_layer_version_obj, _ = Layer_Version.objects.get_or_create(layer = unknown_layer, build = self.internal_state['build'])
+
+        # append it so we don't run into this error again and again
+        self.orm_wrapper.layer_version_objects.append(unknown_layer_version_obj)
+
+        return unknown_layer_version_obj
+
+    def _get_recipe_information_from_taskfile(self, taskfile):
+        localfilepath = taskfile.split(":")[-1]
+        filepath_flags = ":".join(sorted(taskfile.split(":")[:-1]))
+        layer_version_obj = self._get_layer_version_for_path(localfilepath)
+
+
+
+        recipe_info = {}
+        recipe_info['layer_version'] = layer_version_obj
+        recipe_info['file_path'] = localfilepath
+        recipe_info['pathflags'] = filepath_flags
+
+        if recipe_info['file_path'].startswith(recipe_info['layer_version'].local_path):
+            recipe_info['file_path'] = recipe_info['file_path'][len(recipe_info['layer_version'].local_path):].lstrip("/")
+        else:
+            raise RuntimeError("Recipe file path %s is not under layer version at %s" % (recipe_info['file_path'], recipe_info['layer_version'].local_path))
+
+        return recipe_info
+
+    def _get_path_information(self, task_object):
+        assert isinstance(task_object, Task)
+        build_stats_format = "{tmpdir}/buildstats/{target}-{machine}/{buildname}/{package}/"
+        build_stats_path = []
+
+        for t in self.internal_state['targets']:
+            target = t.target
+            machine = self.internal_state['build'].machine
+            buildname = self.internal_state['build'].build_name
+            pe, pv = task_object.recipe.version.split(":",1)
+            if len(pe) > 0:
+                package = task_object.recipe.name + "-" + pe + "_" + pv
+            else:
+                package = task_object.recipe.name + "-" + pv
+
+            build_stats_path.append(build_stats_format.format(tmpdir=self.tmp_dir, target=target,
+                                                     machine=machine, buildname=buildname,
+                                                     package=package))
+
+        return build_stats_path
+
+
+    ################################
+    ## external available methods to store information
+    @staticmethod
+    def _get_data_from_event(event):
+        evdata = None
+        if '_localdata' in vars(event):
+            evdata = event._localdata
+        elif 'data' in vars(event):
+            evdata = event.data
+        else:
+            raise Exception("Event with neither _localdata or data properties")
+        return evdata
+
+    def store_layer_info(self, event):
+        layerinfos = BuildInfoHelper._get_data_from_event(event)
+        self.internal_state['lvs'] = {}
+        for layer in layerinfos:
+            try:
+                self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)] = layerinfos[layer]['version']
+                self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)]['local_path'] = layerinfos[layer]['local_path']
+            except NotExisting as nee:
+                logger.warn("buildinfohelper: cannot identify layer exception:%s ", nee)
+
+
+    def store_started_build(self, event):
+        assert '_pkgs' in vars(event)
+        build_information = self._get_build_information()
+
+        build_obj = self.orm_wrapper.create_build_object(build_information, self.brbe, self.project)
+
+        self.internal_state['build'] = build_obj
+
+        # save layer version information for this build
+        if not 'lvs' in self.internal_state:
+            logger.error("Layer version information not found; Check if the bitbake server was configured to inherit toaster.bbclass.")
+        else:
+            for layer_obj in self.internal_state['lvs']:
+                self.orm_wrapper.get_update_layer_version_object(build_obj, layer_obj, self.internal_state['lvs'][layer_obj])
+
+            del self.internal_state['lvs']
+
+        # create target information
+        target_information = {}
+        target_information['targets'] = event._pkgs
+        target_information['build'] = build_obj
+
+        self.internal_state['targets'] = self.orm_wrapper.create_target_objects(target_information)
+
+        # Save build configuration
+        data = self.server.runCommand(["getAllKeysWithFlags", ["doc", "func"]])[0]
+
+        # convert the paths from absolute to relative to either the build directory or layer checkouts
+        path_prefixes = []
+
+        if self.brbe is not None:
+            _, be_id = self.brbe.split(":")
+            be = BuildEnvironment.objects.get(pk = be_id)
+            path_prefixes.append(be.builddir)
+
+        for layer in sorted(self.orm_wrapper.layer_version_objects, key = lambda x:len(x.local_path), reverse=True):
+            path_prefixes.append(layer.local_path)
+
+        # we strip the prefixes
+        for k in data:
+            if not bool(data[k]['func']):
+                for vh in data[k]['history']:
+                    if not 'documentation.conf' in vh['file']:
+                        abs_file_name = vh['file']
+                        for pp in path_prefixes:
+                            if abs_file_name.startswith(pp + "/"):
+                                vh['file']=abs_file_name[len(pp + "/"):]
+                                break
+
+        # save the variables
+        self.orm_wrapper.save_build_variables(build_obj, data)
+
+        return self.brbe
+
+
+    def update_target_image_file(self, event):
+        evdata = BuildInfoHelper._get_data_from_event(event)
+
+        for t in self.internal_state['targets']:
+            if t.is_image == True:
+                output_files = list(evdata.viewkeys())
+                for output in output_files:
+                    if t.target in output and 'rootfs' in output and not output.endswith(".manifest"):
+                        self.orm_wrapper.save_target_image_file_information(t, output, evdata[output])
+
+    def update_artifact_image_file(self, event):
+        evdata = BuildInfoHelper._get_data_from_event(event)
+        for artifact_path in evdata.keys():
+            self.orm_wrapper.save_artifact_information(self.internal_state['build'], artifact_path, evdata[artifact_path])
+
+    def update_build_information(self, event, errors, warnings, taskfailures):
+        if 'build' in self.internal_state:
+            self.orm_wrapper.update_build_object(self.internal_state['build'], errors, warnings, taskfailures)
+
+
+    def store_license_manifest_path(self, event):
+        deploy_dir = BuildInfoHelper._get_data_from_event(event)['deploy_dir']
+        image_name = BuildInfoHelper._get_data_from_event(event)['image_name']
+        path = deploy_dir + "/licenses/" + image_name + "/license.manifest"
+        for target in self.internal_state['targets']:
+            if target.target in image_name:
+                self.orm_wrapper.update_target_set_license_manifest(target, path)
+
+
+    def store_started_task(self, event):
+        assert isinstance(event, (bb.runqueue.sceneQueueTaskStarted, bb.runqueue.runQueueTaskStarted, bb.runqueue.runQueueTaskSkipped))
+        assert 'taskfile' in vars(event)
+        localfilepath = event.taskfile.split(":")[-1]
+        assert localfilepath.startswith("/")
+
+        identifier = event.taskfile + ":" + event.taskname
+
+        recipe_information = self._get_recipe_information_from_taskfile(event.taskfile)
+        recipe = self.orm_wrapper.get_update_recipe_object(recipe_information, True)
+
+        task_information = self._get_task_information(event, recipe)
+        task_information['outcome'] = Task.OUTCOME_NA
+
+        if isinstance(event, bb.runqueue.runQueueTaskSkipped):
+            assert 'reason' in vars(event)
+            task_information['task_executed'] = False
+            if event.reason == "covered":
+                task_information['outcome'] = Task.OUTCOME_COVERED
+            if event.reason == "existing":
+                task_information['outcome'] = Task.OUTCOME_PREBUILT
+        else:
+            task_information['task_executed'] = True
+            if 'noexec' in vars(event) and event.noexec == True:
+                task_information['task_executed'] = False
+                task_information['outcome'] = Task.OUTCOME_EMPTY
+                task_information['script_type'] = Task.CODING_NA
+
+        # do not assign order numbers to scene tasks
+        if not isinstance(event, bb.runqueue.sceneQueueTaskStarted):
+            self.task_order += 1
+            task_information['order'] = self.task_order
+
+        self.orm_wrapper.get_update_task_object(task_information)
+
+        self.internal_state['taskdata'][identifier] = {
+                        'outcome': task_information['outcome'],
+                    }
+
+
+    def store_tasks_stats(self, event):
+        for (taskfile, taskname, taskstats, recipename) in BuildInfoHelper._get_data_from_event(event):
+            localfilepath = taskfile.split(":")[-1]
+            assert localfilepath.startswith("/")
+
+            recipe_information = self._get_recipe_information_from_taskfile(taskfile)
+            try:
+                if recipe_information['file_path'].startswith(recipe_information['layer_version'].local_path):
+                    recipe_information['file_path'] = recipe_information['file_path'][len(recipe_information['layer_version'].local_path):].lstrip("/")
+
+                recipe_object = Recipe.objects.get(layer_version = recipe_information['layer_version'],
+                            file_path__endswith = recipe_information['file_path'],
+                            name = recipename)
+            except Recipe.DoesNotExist:
+                logger.error("Could not find recipe for recipe_information %s name %s" , pformat(recipe_information), recipename)
+                raise
+
+            task_information = {}
+            task_information['build'] = self.internal_state['build']
+            task_information['recipe'] = recipe_object
+            task_information['task_name'] = taskname
+            task_information['cpu_usage'] = taskstats['cpu_usage']
+            task_information['disk_io'] = taskstats['disk_io']
+            if 'elapsed_time' in taskstats:
+                task_information['elapsed_time'] = taskstats['elapsed_time']
+            self.orm_wrapper.get_update_task_object(task_information, True)  # must exist
+
+    def update_and_store_task(self, event):
+        assert 'taskfile' in vars(event)
+        localfilepath = event.taskfile.split(":")[-1]
+        assert localfilepath.startswith("/")
+
+        identifier = event.taskfile + ":" + event.taskname
+        if not identifier in self.internal_state['taskdata']:
+            if isinstance(event, bb.build.TaskBase):
+                # we do a bit of guessing
+                candidates = [x for x in self.internal_state['taskdata'].keys() if x.endswith(identifier)]
+                if len(candidates) == 1:
+                    identifier = candidates[0]
+
+        assert identifier in self.internal_state['taskdata']
+        identifierlist = identifier.split(":")
+        realtaskfile = ":".join(identifierlist[0:len(identifierlist)-1])
+        recipe_information = self._get_recipe_information_from_taskfile(realtaskfile)
+        recipe = self.orm_wrapper.get_update_recipe_object(recipe_information, True)
+        task_information = self._get_task_information(event,recipe)
+
+        if 'time' in vars(event):
+            if not 'start_time' in self.internal_state['taskdata'][identifier]:
+                self.internal_state['taskdata'][identifier]['start_time'] = event.time
+            else:
+                task_information['end_time'] = event.time
+                task_information['start_time'] = self.internal_state['taskdata'][identifier]['start_time']
+
+        task_information['outcome'] = self.internal_state['taskdata'][identifier]['outcome']
+
+        if 'logfile' in vars(event):
+            task_information['logfile'] = event.logfile
+
+        if '_message' in vars(event):
+            task_information['message'] = event._message
+
+        if 'taskflags' in vars(event):
+            # with TaskStarted, we get even more information
+            if 'python' in event.taskflags.keys() and event.taskflags['python'] == '1':
+                task_information['script_type'] = Task.CODING_PYTHON
+            else:
+                task_information['script_type'] = Task.CODING_SHELL
+
+        if task_information['outcome'] == Task.OUTCOME_NA:
+            if isinstance(event, (bb.runqueue.runQueueTaskCompleted, bb.runqueue.sceneQueueTaskCompleted)):
+                task_information['outcome'] = Task.OUTCOME_SUCCESS
+                del self.internal_state['taskdata'][identifier]
+
+            if isinstance(event, (bb.runqueue.runQueueTaskFailed, bb.runqueue.sceneQueueTaskFailed)):
+                task_information['outcome'] = Task.OUTCOME_FAILED
+                del self.internal_state['taskdata'][identifier]
+
+        if not connection.features.autocommits_when_autocommit_is_off:
+            # we force a sync point here, to get the progress bar to show
+            if self.autocommit_step % 3 == 0:
+                transaction.set_autocommit(True)
+                transaction.set_autocommit(False)
+            self.autocommit_step += 1
+
+        self.orm_wrapper.get_update_task_object(task_information, True) # must exist
+
+
+    def store_missed_state_tasks(self, event):
+        for (fn, taskname, taskhash, sstatefile) in BuildInfoHelper._get_data_from_event(event)['missed']:
+
+            # identifier = fn + taskname + "_setscene"
+            recipe_information = self._get_recipe_information_from_taskfile(fn)
+            recipe = self.orm_wrapper.get_update_recipe_object(recipe_information)
+            mevent = MockEvent()
+            mevent.taskname = taskname
+            mevent.taskhash = taskhash
+            task_information = self._get_task_information(mevent,recipe)
+
+            task_information['start_time'] = timezone.now()
+            task_information['outcome'] = Task.OUTCOME_NA
+            task_information['sstate_checksum'] = taskhash
+            task_information['sstate_result'] = Task.SSTATE_MISS
+            task_information['path_to_sstate_obj'] = sstatefile
+
+            self.orm_wrapper.get_update_task_object(task_information)
+
+        for (fn, taskname, taskhash, sstatefile) in BuildInfoHelper._get_data_from_event(event)['found']:
+
+            # identifier = fn + taskname + "_setscene"
+            recipe_information = self._get_recipe_information_from_taskfile(fn)
+            recipe = self.orm_wrapper.get_update_recipe_object(recipe_information)
+            mevent = MockEvent()
+            mevent.taskname = taskname
+            mevent.taskhash = taskhash
+            task_information = self._get_task_information(mevent,recipe)
+
+            task_information['path_to_sstate_obj'] = sstatefile
+
+            self.orm_wrapper.get_update_task_object(task_information)
+
+
+    def store_target_package_data(self, event):
+        # for all image targets
+        for target in self.internal_state['targets']:
+            if target.is_image:
+                try:
+                    pkgdata = BuildInfoHelper._get_data_from_event(event)['pkgdata']
+                    imgdata = BuildInfoHelper._get_data_from_event(event)['imgdata'][target.target]
+                    self.orm_wrapper.save_target_package_information(self.internal_state['build'], target, imgdata, pkgdata, self.internal_state['recipes'])
+                    filedata = BuildInfoHelper._get_data_from_event(event)['filedata'][target.target]
+                    self.orm_wrapper.save_target_file_information(self.internal_state['build'], target, filedata)
+                except KeyError:
+                    # we must have not got the data for this image, nothing to save
+                    pass
+
+
+
+    def store_dependency_information(self, event):
+        assert '_depgraph' in vars(event)
+        assert 'layer-priorities' in event._depgraph
+        assert 'pn' in event._depgraph
+        assert 'tdepends' in event._depgraph
+
+        errormsg = ""
+
+        # save layer version priorities
+        if 'layer-priorities' in event._depgraph.keys():
+            for lv in event._depgraph['layer-priorities']:
+                (_, path, _, priority) = lv
+                layer_version_obj = self._get_layer_version_for_path(path[1:]) # paths start with a ^
+                assert layer_version_obj is not None
+                layer_version_obj.priority = priority
+                layer_version_obj.save()
+
+        # save recipe information
+        self.internal_state['recipes'] = {}
+        for pn in event._depgraph['pn']:
+
+            file_name = event._depgraph['pn'][pn]['filename'].split(":")[-1]
+            pathflags = ":".join(sorted(event._depgraph['pn'][pn]['filename'].split(":")[:-1]))
+            layer_version_obj = self._get_layer_version_for_path(file_name)
+
+            assert layer_version_obj is not None
+
+            recipe_info = {}
+            recipe_info['name'] = pn
+            recipe_info['layer_version'] = layer_version_obj
+
+            if 'version' in event._depgraph['pn'][pn]:
+                recipe_info['version'] = event._depgraph['pn'][pn]['version'].lstrip(":")
+
+            if 'summary' in event._depgraph['pn'][pn]:
+                recipe_info['summary'] = event._depgraph['pn'][pn]['summary']
+
+            if 'license' in event._depgraph['pn'][pn]:
+                recipe_info['license'] = event._depgraph['pn'][pn]['license']
+
+            if 'description' in event._depgraph['pn'][pn]:
+                recipe_info['description'] = event._depgraph['pn'][pn]['description']
+
+            if 'section' in event._depgraph['pn'][pn]:
+                recipe_info['section'] = event._depgraph['pn'][pn]['section']
+
+            if 'homepage' in event._depgraph['pn'][pn]:
+                recipe_info['homepage'] = event._depgraph['pn'][pn]['homepage']
+
+            if 'bugtracker' in event._depgraph['pn'][pn]:
+                recipe_info['bugtracker'] = event._depgraph['pn'][pn]['bugtracker']
+
+            recipe_info['file_path'] = file_name
+            recipe_info['pathflags'] = pathflags
+
+            if recipe_info['file_path'].startswith(recipe_info['layer_version'].local_path):
+                recipe_info['file_path'] = recipe_info['file_path'][len(recipe_info['layer_version'].local_path):].lstrip("/")
+            else:
+                raise RuntimeError("Recipe file path %s is not under layer version at %s" % (recipe_info['file_path'], recipe_info['layer_version'].local_path))
+
+            recipe = self.orm_wrapper.get_update_recipe_object(recipe_info)
+            recipe.is_image = False
+            if 'inherits' in event._depgraph['pn'][pn].keys():
+                for cls in event._depgraph['pn'][pn]['inherits']:
+                    if cls.endswith('/image.bbclass'):
+                        recipe.is_image = True
+                        break
+            if recipe.is_image:
+                for t in self.internal_state['targets']:
+                    if pn == t.target:
+                        t.is_image = True
+                        t.save()
+            self.internal_state['recipes'][pn] = recipe
+
+        # we'll not get recipes for key w/ values listed in ASSUME_PROVIDED
+
+        assume_provided = self.server.runCommand(["getVariable", "ASSUME_PROVIDED"])[0].split()
+
+        # save recipe dependency
+        # buildtime
+        recipedeps_objects = []
+        for recipe in event._depgraph['depends']:
+            try:
+                target = self.internal_state['recipes'][recipe]
+                for dep in event._depgraph['depends'][recipe]:
+                    dependency = self.internal_state['recipes'][dep]
+                    recipedeps_objects.append(Recipe_Dependency( recipe = target,
+                            depends_on = dependency, dep_type = Recipe_Dependency.TYPE_DEPENDS))
+            except KeyError as e:
+                if e not in assume_provided and not str(e).startswith("virtual/"):
+                    errormsg += "  stpd: KeyError saving recipe dependency for %s, %s \n" % (recipe, e)
+        Recipe_Dependency.objects.bulk_create(recipedeps_objects)
+
+        # save all task information
+        def _save_a_task(taskdesc):
+            spec = re.split(r'\.', taskdesc)
+            pn = ".".join(spec[0:-1])
+            taskname = spec[-1]
+            e = event
+            e.taskname = pn
+            recipe = self.internal_state['recipes'][pn]
+            task_info = self._get_task_information(e, recipe)
+            task_info['task_name'] = taskname
+            task_obj = self.orm_wrapper.get_update_task_object(task_info)
+            return task_obj
+
+        # create tasks
+        tasks = {}
+        for taskdesc in event._depgraph['tdepends']:
+            tasks[taskdesc] = _save_a_task(taskdesc)
+
+        # create dependencies between tasks
+        taskdeps_objects = []
+        for taskdesc in event._depgraph['tdepends']:
+            target = tasks[taskdesc]
+            for taskdep in event._depgraph['tdepends'][taskdesc]:
+                if taskdep not in tasks:
+                    # Fetch tasks info is not collected previously
+                    dep = _save_a_task(taskdep)
+                else:
+                    dep = tasks[taskdep]
+                taskdeps_objects.append(Task_Dependency( task = target, depends_on = dep ))
+        Task_Dependency.objects.bulk_create(taskdeps_objects)
+
+        if len(errormsg) > 0:
+            logger.warn("buildinfohelper: dependency info not identify recipes: \n%s", errormsg)
+
+
+    def store_build_package_information(self, event):
+        package_info = BuildInfoHelper._get_data_from_event(event)
+        self.orm_wrapper.save_build_package_information(self.internal_state['build'],
+                            package_info,
+                            self.internal_state['recipes'],
+                            )
+
+    def _store_build_done(self, errorcode):
+        logger.info("Build exited with errorcode %d", errorcode)
+        br_id, be_id = self.brbe.split(":")
+        be = BuildEnvironment.objects.get(pk = be_id)
+        be.lock = BuildEnvironment.LOCK_LOCK
+        be.save()
+        br = BuildRequest.objects.get(pk = br_id)
+        if errorcode == 0:
+            # request archival of the project artifacts
+            br.state = BuildRequest.REQ_ARCHIVE
+        else:
+            br.state = BuildRequest.REQ_FAILED
+        br.save()
+
+
+    def store_log_error(self, text):
+        mockevent = MockEvent()
+        mockevent.levelno = formatter.ERROR
+        mockevent.msg = text
+        mockevent.pathname = '-- None'
+        mockevent.lineno = LogMessage.ERROR
+        self.store_log_event(mockevent)
+
+    def store_log_exception(self, text, backtrace = ""):
+        mockevent = MockEvent()
+        mockevent.levelno = -1
+        mockevent.msg = text
+        mockevent.pathname = backtrace
+        mockevent.lineno = -1
+        self.store_log_event(mockevent)
+
+
+    def store_log_event(self, event):
+        if event.levelno < formatter.WARNING:
+            return
+
+        if 'args' in vars(event):
+            event.msg = event.msg % event.args
+
+        if not 'build' in self.internal_state:
+            if self.brbe is None:
+                if not 'backlog' in self.internal_state:
+                    self.internal_state['backlog'] = []
+                self.internal_state['backlog'].append(event)
+                return
+            else:   # we're under Toaster control, the build is already created
+                br, _ = self.brbe.split(":")
+                buildrequest = BuildRequest.objects.get(pk = br)
+                self.internal_state['build'] = buildrequest.build
+
+        if 'build' in self.internal_state and 'backlog' in self.internal_state:
+            # if we have a backlog of events, do our best to save them here
+            if len(self.internal_state['backlog']):
+                tempevent = self.internal_state['backlog'].pop()
+                logger.debug(1, "buildinfohelper: Saving stored event %s " % tempevent)
+                self.store_log_event(tempevent)
+            else:
+                logger.info("buildinfohelper: All events saved")
+                del self.internal_state['backlog']
+
+        log_information = {}
+        log_information['build'] = self.internal_state['build']
+        if event.levelno == formatter.ERROR:
+            log_information['level'] = LogMessage.ERROR
+        elif event.levelno == formatter.WARNING:
+            log_information['level'] = LogMessage.WARNING
+        elif event.levelno == -2:   # toaster self-logging
+            log_information['level'] = -2
+        else:
+            log_information['level'] = LogMessage.INFO
+
+        log_information['message'] = event.msg
+        log_information['pathname'] = event.pathname
+        log_information['lineno'] = event.lineno
+        logger.info("Logging error 2: %s", log_information)
+        self.orm_wrapper.create_logmessage(log_information)
+
+    def close(self, errorcode):
+        if self.brbe is not None:
+            self._store_build_done(errorcode)
+
+        if 'backlog' in self.internal_state:
+            if 'build' in self.internal_state:
+                # we save missed events in the database for the current build
+                tempevent = self.internal_state['backlog'].pop()
+                self.store_log_event(tempevent)
+            else:
+                # we have no build, and we still have events; something amazingly wrong happend
+                for event in self.internal_state['backlog']:
+                    logger.error("UNSAVED log: %s", event.msg)
+
+        if not connection.features.autocommits_when_autocommit_is_off:
+            transaction.set_autocommit(True)
diff --git a/bitbake/lib/bb/ui/crumbs/__init__.py b/bitbake/lib/bb/ui/crumbs/__init__.py
new file mode 100644
index 0000000..b7cbe1a
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/__init__.py
@@ -0,0 +1,17 @@
+#
+# Gtk+ UI pieces for BitBake
+#
+# Copyright (C) 2006-2007 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
diff --git a/bitbake/lib/bb/ui/crumbs/builddetailspage.py b/bitbake/lib/bb/ui/crumbs/builddetailspage.py
new file mode 100755
index 0000000..7fc690e
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/builddetailspage.py
@@ -0,0 +1,437 @@
+#!/usr/bin/env python
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2012        Intel Corporation
+#
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import pango
+import gobject
+import bb.process
+from bb.ui.crumbs.progressbar import HobProgressBar
+from bb.ui.crumbs.hobwidget import hic, HobNotebook, HobAltButton, HobWarpCellRendererText, HobButton, HobInfoButton
+from bb.ui.crumbs.runningbuild import RunningBuildTreeView
+from bb.ui.crumbs.runningbuild import BuildFailureTreeView
+from bb.ui.crumbs.hobpages import HobPage
+from bb.ui.crumbs.hobcolor import HobColors
+
+class BuildConfigurationTreeView(gtk.TreeView):
+    def __init__ (self):
+        gtk.TreeView.__init__(self)
+        self.set_rules_hint(False)
+        self.set_headers_visible(False)
+        self.set_property("hover-expand", True)
+        self.get_selection().set_mode(gtk.SELECTION_SINGLE)
+
+        # The icon that indicates whether we're building or failed.
+        renderer0 = gtk.CellRendererText()
+        renderer0.set_property('font-desc', pango.FontDescription('courier bold 12'))
+        col0 = gtk.TreeViewColumn ("Name", renderer0, text=0)
+        self.append_column (col0)
+
+        # The message of configuration.
+        renderer1 = HobWarpCellRendererText(col_number=1)
+        col1 = gtk.TreeViewColumn ("Values", renderer1, text=1)
+        self.append_column (col1)
+
+    def set_vars(self, key="", var=[""]):
+        d = {}
+        if type(var) == str:
+            d = {key: [var]}
+        elif type(var) == list and len(var) > 1:
+            #create the sub item line
+            l = []
+            text = ""
+            for item in var:
+                text = " - " + item
+                l.append(text)
+            d = {key: var}
+
+        return d
+
+    def set_config_model(self, show_vars):
+        listmodel = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
+        parent = None
+        for var in show_vars:
+            for subitem in var.items():
+                name = subitem[0]
+                is_parent = True
+                for value in subitem[1]:
+                    if is_parent:
+                        parent = listmodel.append(parent, (name, value))
+                        is_parent = False
+                    else:
+                        listmodel.append(parent, (None, value))
+                    name = "     - "
+                parent = None
+        # renew the tree model after get the configuration messages
+        self.set_model(listmodel)
+
+    def show(self, src_config_info, src_params):
+        vars = []
+        vars.append(self.set_vars("BB version:", src_params.bb_version))
+        vars.append(self.set_vars("Target arch:", src_params.target_arch))
+        vars.append(self.set_vars("Target OS:", src_params.target_os))
+        vars.append(self.set_vars("Machine:", src_config_info.curr_mach))
+        vars.append(self.set_vars("Distro:", src_config_info.curr_distro))
+        vars.append(self.set_vars("Distro version:", src_params.distro_version))
+        vars.append(self.set_vars("SDK machine:", src_config_info.curr_sdk_machine))
+        vars.append(self.set_vars("Tune features:", src_params.tune_pkgarch))
+        vars.append(self.set_vars("Layers:", src_config_info.layers))
+
+        for path in src_config_info.layers:
+            import os, os.path
+            if os.path.exists(path):
+                branch = bb.process.run('cd %s; git branch | grep "^* " | tr -d "* "' % path)[0]
+                if branch.startswith("fatal:"):
+                    branch = "(unknown)"
+                if branch:
+                    branch = branch.strip('\n')
+                    vars.append(self.set_vars("Branch:", branch))
+                break
+
+        self.set_config_model(vars)
+
+    def reset(self):
+        self.set_model(None)
+
+#
+# BuildDetailsPage
+#
+
+class BuildDetailsPage (HobPage):
+
+    def __init__(self, builder):
+        super(BuildDetailsPage, self).__init__(builder, "Building ...")
+
+        self.num_of_issues = 0
+        self.endpath = (0,)
+        # create visual elements
+        self.create_visual_elements()
+
+    def create_visual_elements(self):
+        # create visual elements
+        self.vbox = gtk.VBox(False, 12)
+
+        self.progress_box = gtk.VBox(False, 12)
+        self.task_status = gtk.Label("\n") # to ensure layout is correct
+        self.task_status.set_alignment(0.0, 0.5)
+        self.progress_box.pack_start(self.task_status, expand=False, fill=False)
+        self.progress_hbox = gtk.HBox(False, 6)
+        self.progress_box.pack_end(self.progress_hbox, expand=True, fill=True)
+        self.progress_bar = HobProgressBar()
+        self.progress_hbox.pack_start(self.progress_bar, expand=True, fill=True)
+        self.stop_button = HobAltButton("Stop")
+        self.stop_button.connect("clicked", self.stop_button_clicked_cb)
+        self.stop_button.set_sensitive(False)
+        self.progress_hbox.pack_end(self.stop_button, expand=False, fill=False)
+
+        self.notebook = HobNotebook()
+        self.config_tv = BuildConfigurationTreeView()
+        self.scrolled_view_config = gtk.ScrolledWindow ()
+        self.scrolled_view_config.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
+        self.scrolled_view_config.add(self.config_tv)
+        self.notebook.append_page(self.scrolled_view_config, "Build configuration")
+
+        self.failure_tv = BuildFailureTreeView()
+        self.failure_model = self.builder.handler.build.model.failure_model()
+        self.failure_tv.set_model(self.failure_model)
+        self.scrolled_view_failure = gtk.ScrolledWindow ()
+        self.scrolled_view_failure.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
+        self.scrolled_view_failure.add(self.failure_tv)
+        self.notebook.append_page(self.scrolled_view_failure, "Issues")
+
+        self.build_tv = RunningBuildTreeView(readonly=True, hob=True)
+        self.build_tv.set_model(self.builder.handler.build.model)
+        self.scrolled_view_build = gtk.ScrolledWindow ()
+        self.scrolled_view_build.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
+        self.scrolled_view_build.add(self.build_tv)
+        self.notebook.append_page(self.scrolled_view_build, "Log")
+
+        self.builder.handler.build.model.connect_after("row-changed", self.scroll_to_present_row, self.scrolled_view_build.get_vadjustment(), self.build_tv)
+
+        self.button_box = gtk.HBox(False, 6)
+        self.back_button = HobAltButton('&lt;&lt; Back')
+        self.back_button.connect("clicked", self.back_button_clicked_cb)
+        self.button_box.pack_start(self.back_button, expand=False, fill=False)
+
+    def update_build_status(self, current, total, task):
+        recipe_path, recipe_task = task.split(", ")
+        recipe = os.path.basename(recipe_path).rstrip(".bb")
+        tsk_msg = "<b>Running task %s of %s:</b> %s\n<b>Recipe:</b> %s" % (current, total, recipe_task, recipe)
+        self.task_status.set_markup(tsk_msg)
+        self.stop_button.set_sensitive(True)
+
+    def reset_build_status(self):
+        self.task_status.set_markup("\n") # to ensure layout is correct
+        self.endpath = (0,)
+
+    def show_issues(self):
+        self.num_of_issues += 1
+        self.notebook.show_indicator_icon("Issues", self.num_of_issues)
+        self.notebook.queue_draw()
+
+    def reset_issues(self):
+        self.num_of_issues = 0
+        self.notebook.hide_indicator_icon("Issues")
+
+    def _remove_all_widget(self):
+        children = self.vbox.get_children() or []
+        for child in children:
+            self.vbox.remove(child)
+        children = self.box_group_area.get_children() or []
+        for child in children:
+            self.box_group_area.remove(child)
+        children = self.get_children() or []
+        for child in children:
+            self.remove(child)
+
+    def add_build_fail_top_bar(self, actions, log_file=None):
+        primary_action = "Edit %s" % actions
+
+        color = HobColors.ERROR
+        build_fail_top = gtk.EventBox()
+        #build_fail_top.set_size_request(-1, 200)
+        build_fail_top.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
+
+        build_fail_tab = gtk.Table(14, 46, True)
+        build_fail_top.add(build_fail_tab)
+
+        icon = gtk.Image()
+        icon_pix_buffer = gtk.gdk.pixbuf_new_from_file(hic.ICON_INDI_ERROR_FILE)
+        icon.set_from_pixbuf(icon_pix_buffer)
+        build_fail_tab.attach(icon, 1, 4, 0, 6)
+
+        label = gtk.Label()
+        label.set_alignment(0.0, 0.5)
+        label.set_markup("<span size='x-large'><b>%s</b></span>" % self.title)
+        build_fail_tab.attach(label, 4, 26, 0, 6)
+
+        label = gtk.Label()
+        label.set_alignment(0.0, 0.5)
+        # Ensure variable disk_full is defined
+        if not hasattr(self.builder, 'disk_full'):
+            self.builder.disk_full = False
+
+        if self.builder.disk_full:
+            markup = "<span size='medium'>There is no disk space left, so Hob cannot finish building your image. Free up some disk space\n"
+            markup += "and restart the build. Check the \"Issues\" tab for more details</span>"
+            label.set_markup(markup)
+        else:
+            label.set_markup("<span size='medium'>Check the \"Issues\" information for more details</span>")
+        build_fail_tab.attach(label, 4, 40, 4, 9)
+
+        # create button 'Edit packages'
+        action_button = HobButton(primary_action)
+        #action_button.set_size_request(-1, 40)
+        action_button.set_tooltip_text("Edit the %s parameters" % actions)
+        action_button.connect('clicked', self.failure_primary_action_button_clicked_cb, primary_action)
+
+        if log_file:
+            open_log_button = HobAltButton("Open log")
+            open_log_button.set_relief(gtk.RELIEF_HALF)
+            open_log_button.set_tooltip_text("Open the build's log file")
+            open_log_button.connect('clicked', self.open_log_button_clicked_cb, log_file)
+
+        attach_pos = (24 if log_file else 14)
+        file_bug_button = HobAltButton('File a bug')
+        file_bug_button.set_relief(gtk.RELIEF_HALF)
+        file_bug_button.set_tooltip_text("Open the Yocto Project bug tracking website")
+        file_bug_button.connect('clicked', self.failure_activate_file_bug_link_cb)
+
+        if not self.builder.disk_full:
+            build_fail_tab.attach(action_button, 4, 13, 9, 12)
+            if log_file:
+                build_fail_tab.attach(open_log_button, 14, 23, 9, 12)
+            build_fail_tab.attach(file_bug_button, attach_pos, attach_pos + 9, 9, 12)
+
+        else:
+            restart_build = HobButton("Restart the build")
+            restart_build.set_tooltip_text("Restart the build")
+            restart_build.connect('clicked', self.restart_build_button_clicked_cb)
+
+            build_fail_tab.attach(restart_build, 4, 13, 9, 12)
+            build_fail_tab.attach(action_button, 14, 23, 9, 12)
+            if log_file:
+                build_fail_tab.attach(open_log_button, attach_pos, attach_pos + 9, 9, 12)
+
+        self.builder.disk_full = False
+        return build_fail_top
+
+    def show_fail_page(self, title):
+        self._remove_all_widget()
+        self.title = "Hob cannot build your %s" % title
+
+        self.build_fail_bar = self.add_build_fail_top_bar(title, self.builder.current_logfile)
+
+        self.pack_start(self.group_align, expand=True, fill=True)
+        self.box_group_area.pack_start(self.build_fail_bar, expand=False, fill=False)
+        self.box_group_area.pack_start(self.vbox, expand=True, fill=True)
+
+        self.vbox.pack_start(self.notebook, expand=True, fill=True)
+        self.show_all()
+        self.notebook.set_page("Issues")
+        self.back_button.hide()
+
+    def add_build_stop_top_bar(self, action, log_file=None):
+        color = HobColors.LIGHT_GRAY
+        build_stop_top = gtk.EventBox()
+        #build_stop_top.set_size_request(-1, 200)
+        build_stop_top.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
+        build_stop_top.set_flags(gtk.CAN_DEFAULT)
+        build_stop_top.grab_default()
+
+        build_stop_tab = gtk.Table(11, 46, True)
+        build_stop_top.add(build_stop_tab)
+
+        icon = gtk.Image()
+        icon_pix_buffer = gtk.gdk.pixbuf_new_from_file(hic.ICON_INFO_HOVER_FILE)
+        icon.set_from_pixbuf(icon_pix_buffer)
+        build_stop_tab.attach(icon, 1, 4, 0, 6)
+
+        label = gtk.Label()
+        label.set_alignment(0.0, 0.5)
+        label.set_markup("<span size='x-large'><b>%s</b></span>" % self.title)
+        build_stop_tab.attach(label, 4, 26, 0, 6)
+
+        action_button = HobButton("Edit %s" % action)
+        action_button.set_size_request(-1, 40)
+        if action == "image":
+            action_button.set_tooltip_text("Edit the image parameters")
+        elif action == "recipes":
+            action_button.set_tooltip_text("Edit the included recipes")
+        elif action == "packages":
+            action_button.set_tooltip_text("Edit the included packages")
+        action_button.connect('clicked', self.stop_primary_action_button_clicked_cb, action)
+        build_stop_tab.attach(action_button, 4, 13, 6, 9)
+
+        if log_file:
+            open_log_button = HobAltButton("Open log")
+            open_log_button.set_relief(gtk.RELIEF_HALF)
+            open_log_button.set_tooltip_text("Open the build's log file")
+            open_log_button.connect('clicked', self.open_log_button_clicked_cb, log_file)
+            build_stop_tab.attach(open_log_button, 14, 23, 6, 9)
+
+        attach_pos = (24 if log_file else 14)
+        build_button = HobAltButton("Build new image")
+        #build_button.set_size_request(-1, 40)
+        build_button.set_tooltip_text("Create a new image from scratch")
+        build_button.connect('clicked', self.new_image_button_clicked_cb)
+        build_stop_tab.attach(build_button, attach_pos, attach_pos + 9, 6, 9)
+
+        return build_stop_top, action_button
+
+    def show_stop_page(self, action):
+        self._remove_all_widget()
+        self.title = "Build stopped"
+        self.build_stop_bar, action_button = self.add_build_stop_top_bar(action, self.builder.current_logfile)
+
+        self.pack_start(self.group_align, expand=True, fill=True)
+        self.box_group_area.pack_start(self.build_stop_bar, expand=False, fill=False)
+        self.box_group_area.pack_start(self.vbox, expand=True, fill=True)
+
+        self.vbox.pack_start(self.notebook, expand=True, fill=True)
+        self.show_all()
+        self.back_button.hide()
+        return action_button
+
+    def show_page(self, step):
+        self._remove_all_widget()
+        if step == self.builder.PACKAGE_GENERATING or step == self.builder.FAST_IMAGE_GENERATING:
+            self.title = "Building packages ..."
+        else:
+            self.title = "Building image ..."
+        self.build_details_top = self.add_onto_top_bar(None)
+        self.pack_start(self.build_details_top, expand=False, fill=False)
+        self.pack_start(self.group_align, expand=True, fill=True)
+
+        self.box_group_area.pack_start(self.vbox, expand=True, fill=True)
+
+        self.progress_bar.reset()
+        self.config_tv.reset()
+        self.vbox.pack_start(self.progress_box, expand=False, fill=False)
+
+        self.vbox.pack_start(self.notebook, expand=True, fill=True)
+
+        self.box_group_area.pack_end(self.button_box, expand=False, fill=False)
+        self.show_all()
+        self.notebook.set_page("Log")
+        self.back_button.hide()
+
+        self.reset_build_status()
+        self.reset_issues()
+
+    def update_progress_bar(self, title, fraction, status=None):
+        self.progress_bar.update(fraction)
+        self.progress_bar.set_title(title)
+        self.progress_bar.set_rcstyle(status)
+
+    def back_button_clicked_cb(self, button):
+        self.builder.show_configuration()
+
+    def new_image_button_clicked_cb(self, button):
+        self.builder.reset()
+
+    def show_back_button(self):
+        self.back_button.show()
+
+    def stop_button_clicked_cb(self, button):
+        self.builder.stop_build()
+
+    def hide_stop_button(self):
+        self.stop_button.set_sensitive(False)
+        self.stop_button.hide()
+
+    def scroll_to_present_row(self, model, path, iter, v_adj, treeview):
+        if treeview and v_adj:
+            if path[0] > self.endpath[0]: # check the event is a new row append or not
+                self.endpath = path
+                # check the gtk.adjustment position is at end boundary or not
+                if (v_adj.upper <= v_adj.page_size) or (v_adj.value == v_adj.upper - v_adj.page_size):
+                    treeview.scroll_to_cell(path)
+
+    def show_configurations(self, configurations, params):
+        self.config_tv.show(configurations, params)
+
+    def failure_primary_action_button_clicked_cb(self, button, action):
+        if "Edit recipes" in action:
+            self.builder.show_recipes()
+        elif "Edit packages" in action:
+            self.builder.show_packages()
+        elif "Edit image" in action:
+            self.builder.show_configuration()
+
+    def restart_build_button_clicked_cb(self, button):
+        self.builder.just_bake()
+
+    def stop_primary_action_button_clicked_cb(self, button, action):
+        if "recipes" in action:
+            self.builder.show_recipes()
+        elif "packages" in action:
+            self.builder.show_packages()
+        elif "image" in action:
+            self.builder.show_configuration()
+
+    def open_log_button_clicked_cb(self, button, log_file):
+        if log_file:
+            log_file = "file:///" + log_file
+            gtk.show_uri(screen=button.get_screen(), uri=log_file, timestamp=0)
+
+    def failure_activate_file_bug_link_cb(self, button):
+        button.child.emit('activate-link', "http://bugzilla.yoctoproject.org")
diff --git a/bitbake/lib/bb/ui/crumbs/builder.py b/bitbake/lib/bb/ui/crumbs/builder.py
new file mode 100755
index 0000000..dcc4104
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/builder.py
@@ -0,0 +1,1475 @@
+#!/usr/bin/env python
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012   Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import glib
+import gtk, gobject
+import copy
+import os
+import subprocess
+import shlex
+import re
+import logging
+import sys
+import signal
+import time
+from bb.ui.crumbs.imageconfigurationpage import ImageConfigurationPage
+from bb.ui.crumbs.recipeselectionpage import RecipeSelectionPage
+from bb.ui.crumbs.packageselectionpage import PackageSelectionPage
+from bb.ui.crumbs.builddetailspage import BuildDetailsPage
+from bb.ui.crumbs.imagedetailspage import ImageDetailsPage
+from bb.ui.crumbs.sanitycheckpage import SanityCheckPage
+from bb.ui.crumbs.hobwidget import hwc, HobButton, HobAltButton
+from bb.ui.crumbs.persistenttooltip import PersistentTooltip
+import bb.ui.crumbs.utils
+from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog
+from bb.ui.crumbs.hig.simplesettingsdialog import SimpleSettingsDialog
+from bb.ui.crumbs.hig.advancedsettingsdialog import AdvancedSettingsDialog
+from bb.ui.crumbs.hig.deployimagedialog import DeployImageDialog
+from bb.ui.crumbs.hig.layerselectiondialog import LayerSelectionDialog
+from bb.ui.crumbs.hig.imageselectiondialog import ImageSelectionDialog
+from bb.ui.crumbs.hig.parsingwarningsdialog import ParsingWarningsDialog
+from bb.ui.crumbs.hig.propertydialog import PropertyDialog
+
+hobVer = 20120808
+
+class Configuration:
+    '''Represents the data structure of configuration.'''
+
+    @classmethod
+    def parse_proxy_string(cls, proxy):
+        pattern = "^\s*((http|https|ftp|socks|cvs)://)?((\S+):(\S+)@)?([^\s:]+)(:(\d+))?/?"
+        match = re.search(pattern, proxy)
+        if match:
+            return match.group(2), match.group(4), match.group(5), match.group(6), match.group(8)
+        else:
+            return None, None, None, "", ""
+
+    @classmethod
+    def make_host_string(cls, prot, user, passwd, host, default_prot=""):
+        if host == None or host == "":
+            return ""
+
+        passwd = passwd or ""
+
+        if user != None and user != "":
+            if prot == None or prot == "":
+                prot = default_prot
+            return prot + "://" + user + ":" + passwd + "@" + host
+        else:
+            if prot == None or prot == "":
+                return host
+            else:
+                return prot + "://" + host
+
+    @classmethod
+    def make_port_string(cls, port):
+        port = port or ""
+        return port
+
+    @classmethod
+    def make_proxy_string(cls, prot, user, passwd, host, port, default_prot=""):
+        if host == None or host == "":# or port == None or port == "":
+            return ""
+
+        return Configuration.make_host_string(prot, user, passwd, host, default_prot) + (":" + Configuration.make_port_string(port) if port else "")
+
+    def __init__(self):
+        self.curr_mach = ""
+        self.selected_image = None
+        # settings
+        self.curr_distro = ""
+        self.dldir = self.sstatedir = self.sstatemirror = ""
+        self.pmake = self.bbthread = 0
+        self.curr_package_format = ""
+        self.image_rootfs_size = self.image_extra_size = 0
+        self.image_overhead_factor = 1
+        self.incompat_license = ""
+        self.curr_sdk_machine = ""
+        self.conf_version = self.lconf_version = ""
+        self.extra_setting = {}
+        self.toolchain_build = False
+        self.image_fstypes = ""
+        self.image_size = None
+        self.image_packages = []
+        # bblayers.conf
+        self.layers = []
+        # image/recipes/packages
+        self.clear_selection()
+
+        self.user_selected_packages = []
+
+        self.default_task = "build"
+
+        # proxy settings
+        self.enable_proxy = None
+        self.same_proxy = False
+        self.proxies = {
+            "http"  : [None, None, None, "", ""],  # protocol : [prot, user, passwd, host, port]
+            "https" : [None, None, None, "", ""],
+            "ftp"   : [None, None, None, "", ""],
+            "socks" : [None, None, None, "", ""],
+            "cvs"   : [None, None, None, "", ""],
+        }
+
+    def clear_selection(self):
+        self.selected_recipes = []
+        self.selected_packages = []
+        self.initial_selected_image = None
+        self.initial_selected_packages = []
+        self.initial_user_selected_packages = []
+
+    def split_proxy(self, protocol, proxy):
+        entry = []
+        prot, user, passwd, host, port = Configuration.parse_proxy_string(proxy)
+        entry.append(prot)
+        entry.append(user)
+        entry.append(passwd)
+        entry.append(host)
+        entry.append(port)
+        self.proxies[protocol] = entry
+
+    def combine_proxy(self, protocol):
+        entry = self.proxies[protocol]
+        return Configuration.make_proxy_string(entry[0], entry[1], entry[2], entry[3], entry[4], protocol)
+
+    def combine_host_only(self, protocol):
+        entry = self.proxies[protocol]
+        return Configuration.make_host_string(entry[0], entry[1], entry[2], entry[3], protocol)
+
+    def combine_port_only(self, protocol):
+        entry = self.proxies[protocol]
+        return Configuration.make_port_string(entry[4])
+
+    def update(self, params):
+        # settings
+        self.curr_distro = params["distro"]
+        self.dldir = params["dldir"]
+        self.sstatedir = params["sstatedir"]
+        self.sstatemirror = params["sstatemirror"]
+        self.pmake = int(params["pmake"].split()[1])
+        self.bbthread = params["bbthread"]
+        self.curr_package_format = " ".join(params["pclass"].split("package_")).strip()
+        self.image_rootfs_size = params["image_rootfs_size"]
+        self.image_extra_size = params["image_extra_size"]
+        self.image_overhead_factor = params['image_overhead_factor']
+        self.incompat_license = params["incompat_license"]
+        self.curr_sdk_machine = params["sdk_machine"]
+        self.conf_version = params["conf_version"]
+        self.lconf_version = params["lconf_version"]
+        self.image_fstypes = params["image_fstypes"]
+        # self.extra_setting/self.toolchain_build
+        # bblayers.conf
+        self.layers = params["layer"].split()
+        self.layers_non_removable = params["layers_non_removable"].split()
+        self.default_task = params["default_task"]
+
+        # proxy settings
+        self.enable_proxy = params["http_proxy"] != "" or params["https_proxy"] != "" \
+            or params["ftp_proxy"] != "" or params["socks_proxy"] != "" \
+            or params["cvs_proxy_host"] != "" or params["cvs_proxy_port"] != ""
+        self.split_proxy("http", params["http_proxy"])
+        self.split_proxy("https", params["https_proxy"])
+        self.split_proxy("ftp", params["ftp_proxy"])
+        self.split_proxy("socks", params["socks_proxy"])
+        self.split_proxy("cvs", params["cvs_proxy_host"] + ":" + params["cvs_proxy_port"])
+
+    def save(self, handler, defaults=False):
+        # bblayers.conf
+        handler.set_var_in_file("BBLAYERS", self.layers, "bblayers.conf")
+        # local.conf
+        if not defaults:
+            handler.early_assign_var_in_file("MACHINE", self.curr_mach, "local.conf")
+        handler.set_var_in_file("DISTRO", self.curr_distro, "local.conf")
+        handler.set_var_in_file("DL_DIR", self.dldir, "local.conf")
+        handler.set_var_in_file("SSTATE_DIR", self.sstatedir, "local.conf")
+        sstate_mirror_list = self.sstatemirror.split("\\n ")
+        sstate_mirror_list_modified = []
+        for mirror in sstate_mirror_list:
+            if mirror != "":
+                mirror = mirror + "\\n"
+                sstate_mirror_list_modified.append(mirror)
+        handler.set_var_in_file("SSTATE_MIRRORS", sstate_mirror_list_modified, "local.conf")
+        handler.set_var_in_file("PARALLEL_MAKE", "-j %s" % self.pmake, "local.conf")
+        handler.set_var_in_file("BB_NUMBER_THREADS", self.bbthread, "local.conf")
+        handler.set_var_in_file("PACKAGE_CLASSES", " ".join(["package_" + i for i in self.curr_package_format.split()]), "local.conf")
+        handler.set_var_in_file("IMAGE_ROOTFS_SIZE", self.image_rootfs_size, "local.conf")
+        handler.set_var_in_file("IMAGE_EXTRA_SPACE", self.image_extra_size, "local.conf")
+        handler.set_var_in_file("INCOMPATIBLE_LICENSE", self.incompat_license, "local.conf")
+        handler.set_var_in_file("SDKMACHINE", self.curr_sdk_machine, "local.conf")
+        handler.set_var_in_file("CONF_VERSION", self.conf_version, "local.conf")
+        handler.set_var_in_file("LCONF_VERSION", self.lconf_version, "bblayers.conf")
+        handler.set_extra_config(self.extra_setting)
+        handler.set_var_in_file("TOOLCHAIN_BUILD", self.toolchain_build, "local.conf")
+        handler.set_var_in_file("IMAGE_FSTYPES", self.image_fstypes, "local.conf")
+        if not defaults:
+            # image/recipes/packages
+            handler.set_var_in_file("__SELECTED_IMAGE__", self.selected_image, "local.conf")
+            handler.set_var_in_file("DEPENDS", self.selected_recipes, "local.conf")
+            handler.set_var_in_file("IMAGE_INSTALL", self.user_selected_packages, "local.conf")
+        # proxy
+        if self.enable_proxy == True:
+            handler.set_var_in_file("http_proxy", self.combine_proxy("http"), "local.conf")
+            handler.set_var_in_file("https_proxy", self.combine_proxy("https"), "local.conf")
+            handler.set_var_in_file("ftp_proxy", self.combine_proxy("ftp"), "local.conf")
+            handler.set_var_in_file("all_proxy", self.combine_proxy("socks"), "local.conf")
+            handler.set_var_in_file("CVS_PROXY_HOST", self.combine_host_only("cvs"), "local.conf")
+            handler.set_var_in_file("CVS_PROXY_PORT", self.combine_port_only("cvs"), "local.conf")
+        else:
+            handler.set_var_in_file("http_proxy", "", "local.conf")
+            handler.set_var_in_file("https_proxy", "", "local.conf")
+            handler.set_var_in_file("ftp_proxy", "", "local.conf")
+            handler.set_var_in_file("all_proxy", "", "local.conf")
+            handler.set_var_in_file("CVS_PROXY_HOST", "", "local.conf")
+            handler.set_var_in_file("CVS_PROXY_PORT", "", "local.conf")
+
+    def __str__(self):
+        s = "VERSION: '%s', BBLAYERS: '%s', MACHINE: '%s', DISTRO: '%s', DL_DIR: '%s'," % \
+            (hobVer, " ".join(self.layers), self.curr_mach, self.curr_distro, self.dldir )
+        s += "SSTATE_DIR: '%s', SSTATE_MIRROR: '%s', PARALLEL_MAKE: '-j %s', BB_NUMBER_THREADS: '%s', PACKAGE_CLASSES: '%s', " % \
+            (self.sstatedir, self.sstatemirror, self.pmake, self.bbthread, " ".join(["package_" + i for i in self.curr_package_format.split()]))
+        s += "IMAGE_ROOTFS_SIZE: '%s', IMAGE_EXTRA_SPACE: '%s', INCOMPATIBLE_LICENSE: '%s', SDKMACHINE: '%s', CONF_VERSION: '%s', " % \
+            (self.image_rootfs_size, self.image_extra_size, self.incompat_license, self.curr_sdk_machine, self.conf_version)
+        s += "LCONF_VERSION: '%s', EXTRA_SETTING: '%s', TOOLCHAIN_BUILD: '%s', IMAGE_FSTYPES: '%s', __SELECTED_IMAGE__: '%s', " % \
+            (self.lconf_version, self.extra_setting, self.toolchain_build, self.image_fstypes, self.selected_image)
+        s += "DEPENDS: '%s', IMAGE_INSTALL: '%s', enable_proxy: '%s', use_same_proxy: '%s', http_proxy: '%s', " % \
+            (self.selected_recipes, self.user_selected_packages, self.enable_proxy, self.same_proxy, self.combine_proxy("http"))
+        s += "https_proxy: '%s', ftp_proxy: '%s', all_proxy: '%s', CVS_PROXY_HOST: '%s', CVS_PROXY_PORT: '%s'" % \
+            (self.combine_proxy("https"), self.combine_proxy("ftp"), self.combine_proxy("socks"),
+             self.combine_host_only("cvs"), self.combine_port_only("cvs"))
+        return s
+
+class Parameters:
+    '''Represents other variables like available machines, etc.'''
+
+    def __init__(self):
+        # Variables
+        self.max_threads = 65535
+        self.core_base = ""
+        self.image_addr = ""
+        self.image_types = []
+        self.runnable_image_types = []
+        self.runnable_machine_patterns = []
+        self.deployable_image_types = []
+        self.tmpdir = ""
+
+        self.all_machines = []
+        self.all_package_formats = []
+        self.all_distros = []
+        self.all_sdk_machines = []
+        self.all_layers = []
+        self.image_names = []
+        self.image_white_pattern = ""
+        self.image_black_pattern = ""
+
+        # for build log to show
+        self.bb_version = ""
+        self.target_arch = ""
+        self.target_os = ""
+        self.distro_version = ""
+        self.tune_pkgarch = ""
+
+    def update(self, params):
+        self.max_threads = params["max_threads"]
+        self.core_base = params["core_base"]
+        self.image_addr = params["image_addr"]
+        self.image_types = params["image_types"].split()
+        self.runnable_image_types = params["runnable_image_types"].split()
+        self.runnable_machine_patterns = params["runnable_machine_patterns"].split()
+        self.deployable_image_types = params["deployable_image_types"].split()
+        self.tmpdir = params["tmpdir"]
+        self.image_white_pattern = params["image_white_pattern"]
+        self.image_black_pattern = params["image_black_pattern"]
+        self.kernel_image_type = params["kernel_image_type"]
+        # for build log to show
+        self.bb_version = params["bb_version"]
+        self.target_arch = params["target_arch"]
+        self.target_os = params["target_os"]
+        self.distro_version = params["distro_version"]
+        self.tune_pkgarch = params["tune_pkgarch"]
+
+def hob_conf_filter(fn, data):
+    if fn.endswith("/local.conf"):
+        distro = data.getVar("DISTRO_HOB", False)
+        if distro:
+            if distro != "defaultsetup":
+                data.setVar("DISTRO", distro)
+            else:
+                data.delVar("DISTRO")
+
+        keys = ["MACHINE_HOB", "SDKMACHINE_HOB", "PACKAGE_CLASSES_HOB", \
+                "BB_NUMBER_THREADS_HOB", "PARALLEL_MAKE_HOB", "DL_DIR_HOB", \
+                "SSTATE_DIR_HOB", "SSTATE_MIRRORS_HOB", "INCOMPATIBLE_LICENSE_HOB"]
+        for key in keys:
+            var_hob = data.getVar(key, False)
+            if var_hob:
+                data.setVar(key.split("_HOB")[0], var_hob)
+        return
+
+    if fn.endswith("/bblayers.conf"):
+        layers = data.getVar("BBLAYERS_HOB", False)
+        if layers:
+            data.setVar("BBLAYERS", layers)
+        return
+
+class Builder(gtk.Window):
+
+    (INITIAL_CHECKS,
+     MACHINE_SELECTION,
+     RCPPKGINFO_POPULATING,
+     RCPPKGINFO_POPULATED,
+     BASEIMG_SELECTED,
+     RECIPE_SELECTION,
+     PACKAGE_GENERATING,
+     PACKAGE_GENERATED,
+     PACKAGE_SELECTION,
+     FAST_IMAGE_GENERATING,
+     IMAGE_GENERATING,
+     IMAGE_GENERATED,
+     MY_IMAGE_OPENED,
+     BACK,
+     END_NOOP) = range(15)
+
+    (SANITY_CHECK,
+     IMAGE_CONFIGURATION,
+     RECIPE_DETAILS,
+     BUILD_DETAILS,
+     PACKAGE_DETAILS,
+     IMAGE_DETAILS,
+     END_TAB) = range(7)
+
+    __step2page__ = {
+        INITIAL_CHECKS        : SANITY_CHECK,
+        MACHINE_SELECTION     : IMAGE_CONFIGURATION,
+        RCPPKGINFO_POPULATING : IMAGE_CONFIGURATION,
+        RCPPKGINFO_POPULATED  : IMAGE_CONFIGURATION,
+        BASEIMG_SELECTED      : IMAGE_CONFIGURATION,
+        RECIPE_SELECTION      : RECIPE_DETAILS,
+        PACKAGE_GENERATING    : BUILD_DETAILS,
+        PACKAGE_GENERATED     : PACKAGE_DETAILS,
+        PACKAGE_SELECTION     : PACKAGE_DETAILS,
+        FAST_IMAGE_GENERATING : BUILD_DETAILS,
+        IMAGE_GENERATING      : BUILD_DETAILS,
+        IMAGE_GENERATED       : IMAGE_DETAILS,
+        MY_IMAGE_OPENED       : IMAGE_DETAILS,
+        END_NOOP              : None,
+    }
+
+    SANITY_CHECK_MIN_DISPLAY_TIME = 5
+
+    def __init__(self, hobHandler, recipe_model, package_model):
+        super(Builder, self).__init__()
+
+        self.hob_image = "hob-image"
+
+        # handler
+        self.handler = hobHandler
+
+        # logger
+        self.logger = logging.getLogger("BitBake")
+        self.consolelog = None
+        self.current_logfile = None
+
+        # configuration and parameters
+        self.configuration = Configuration()
+        self.parameters = Parameters()
+
+        # build step
+        self.current_step = None
+        self.previous_step = None
+
+        self.stopping = False
+
+        # recipe model and package model
+        self.recipe_model = recipe_model
+        self.package_model = package_model
+
+        # Indicate whether user has customized the image
+        self.customized = False
+
+        # Indicate whether the UI is working
+        self.sensitive = True
+
+        # Indicate whether the sanity check ran
+        self.sanity_checked = False
+
+        # save parsing warnings
+        self.parsing_warnings = []
+
+        # create visual elements
+        self.create_visual_elements()
+
+        # connect the signals to functions
+        self.connect("delete-event", self.destroy_window_cb)
+        self.recipe_model.connect ("recipe-selection-changed",  self.recipelist_changed_cb)
+        self.package_model.connect("package-selection-changed", self.packagelist_changed_cb)
+        self.handler.connect("config-updated",           self.handler_config_updated_cb)
+        self.handler.connect("package-formats-updated",  self.handler_package_formats_updated_cb)
+        self.handler.connect("parsing-started",          self.handler_parsing_started_cb)
+        self.handler.connect("parsing",                  self.handler_parsing_cb)
+        self.handler.connect("parsing-completed",        self.handler_parsing_completed_cb)
+        self.handler.build.connect("build-started",      self.handler_build_started_cb)
+        self.handler.build.connect("build-succeeded",    self.handler_build_succeeded_cb)
+        self.handler.build.connect("build-failed",       self.handler_build_failed_cb)
+        self.handler.build.connect("build-aborted",      self.handler_build_aborted_cb)
+        self.handler.build.connect("task-started",       self.handler_task_started_cb)
+        self.handler.build.connect("disk-full",          self.handler_disk_full_cb)
+        self.handler.build.connect("log-error",          self.handler_build_failure_cb)
+        self.handler.build.connect("log-warning",        self.handler_build_failure_cb)
+        self.handler.build.connect("log",                self.handler_build_log_cb)
+        self.handler.build.connect("no-provider",        self.handler_no_provider_cb)
+        self.handler.connect("generating-data",          self.handler_generating_data_cb)
+        self.handler.connect("data-generated",           self.handler_data_generated_cb)
+        self.handler.connect("command-succeeded",        self.handler_command_succeeded_cb)
+        self.handler.connect("command-failed",           self.handler_command_failed_cb)
+        self.handler.connect("parsing-warning",          self.handler_parsing_warning_cb)
+        self.handler.connect("sanity-failed",            self.handler_sanity_failed_cb)
+        self.handler.connect("recipe-populated",         self.handler_recipe_populated_cb)
+        self.handler.connect("package-populated",        self.handler_package_populated_cb)
+
+        self.handler.append_to_bbfiles("${TOPDIR}/recipes/images/custom/*.bb")
+        self.handler.append_to_bbfiles("${TOPDIR}/recipes/images/*.bb")
+        self.initiate_new_build_async()
+
+        signal.signal(signal.SIGINT, self.event_handle_SIGINT)
+
+    def create_visual_elements(self):
+        self.set_title("Hob")
+        self.set_icon_name("applications-development")
+        self.set_resizable(True)
+
+        try:
+            window_width = self.get_screen().get_width()
+            window_height = self.get_screen().get_height()
+        except AttributeError:
+            print "Please set DISPLAY variable before running Hob."
+            sys.exit(1)
+
+        if window_width >= hwc.MAIN_WIN_WIDTH:
+            window_width = hwc.MAIN_WIN_WIDTH
+            window_height = hwc.MAIN_WIN_HEIGHT
+        self.set_size_request(window_width, window_height)
+
+        self.vbox = gtk.VBox(False, 0)
+        self.vbox.set_border_width(0)
+        self.add(self.vbox)
+
+        # create pages
+        self.image_configuration_page = ImageConfigurationPage(self)
+        self.recipe_details_page      = RecipeSelectionPage(self)
+        self.build_details_page       = BuildDetailsPage(self)
+        self.package_details_page     = PackageSelectionPage(self)
+        self.image_details_page       = ImageDetailsPage(self)
+        self.sanity_check_page        = SanityCheckPage(self)
+        self.display_sanity_check = False
+        self.sanity_check_post_func = False
+        self.had_network_error = False
+
+        self.nb = gtk.Notebook()
+        self.nb.set_show_tabs(False)
+        self.nb.insert_page(self.sanity_check_page,        None, self.SANITY_CHECK)
+        self.nb.insert_page(self.image_configuration_page, None, self.IMAGE_CONFIGURATION)
+        self.nb.insert_page(self.recipe_details_page,      None, self.RECIPE_DETAILS)
+        self.nb.insert_page(self.build_details_page,       None, self.BUILD_DETAILS)
+        self.nb.insert_page(self.package_details_page,     None, self.PACKAGE_DETAILS)
+        self.nb.insert_page(self.image_details_page,       None, self.IMAGE_DETAILS)
+        self.vbox.pack_start(self.nb, expand=True, fill=True)
+
+        self.show_all()
+        self.nb.set_current_page(0)
+
+    def sanity_check_timeout(self):
+        # The minimum time for showing the 'sanity check' page has passe
+        # If someone set the 'sanity_check_post_step' meanwhile, execute it now
+        self.display_sanity_check = False
+        if self.sanity_check_post_func:
+          temp = self.sanity_check_post_func
+          self.sanity_check_post_func = None
+          temp()
+        return False
+
+    def show_sanity_check_page(self):
+        # This window must stay on screen for at least 5 seconds, according to the design document
+        self.nb.set_current_page(self.SANITY_CHECK)
+        self.sanity_check_post_step = None
+        self.display_sanity_check = True
+        self.sanity_check_page.start()
+        gobject.timeout_add(self.SANITY_CHECK_MIN_DISPLAY_TIME * 1000, self.sanity_check_timeout)
+
+    def execute_after_sanity_check(self, func):
+        if not self.display_sanity_check:
+          func()
+        else:
+          self.sanity_check_post_func = func
+
+    def generate_configuration(self):
+        if not self.sanity_checked:
+            self.show_sanity_check_page()
+        self.handler.generate_configuration()
+
+    def initiate_new_build_async(self):
+        self.configuration.selected_image = None
+        self.switch_page(self.MACHINE_SELECTION)
+        self.handler.init_cooker()
+        self.handler.set_extra_inherit("image_types")
+        self.generate_configuration()
+
+    def update_config_async(self):
+        self.set_user_config()
+        self.generate_configuration()
+        self.switch_page(self.MACHINE_SELECTION)
+
+    def sanity_check(self):
+        self.handler.trigger_sanity_check()
+
+    def populate_recipe_package_info_async(self):
+        self.switch_page(self.RCPPKGINFO_POPULATING)
+        # Parse recipes
+        self.set_user_config()
+        self.handler.generate_recipes()
+
+    def generate_packages_async(self, log = False):
+        self.switch_page(self.PACKAGE_GENERATING)
+        if log:
+            self.current_logfile = self.handler.get_logfile()
+            self.do_log(self.current_logfile)
+        # Build packages
+        _, all_recipes = self.recipe_model.get_selected_recipes()
+        self.set_user_config()
+        self.handler.reset_build()
+        self.handler.generate_packages(all_recipes, self.configuration.default_task)
+
+    def restore_initial_selected_packages(self):
+        self.package_model.set_selected_packages(self.configuration.initial_user_selected_packages, True)
+        self.package_model.set_selected_packages(self.configuration.initial_selected_packages)
+        for package in self.configuration.selected_packages:
+            if package not in self.configuration.initial_selected_packages:
+                self.package_model.exclude_item(self.package_model.find_path_for_item(package))
+
+    def fast_generate_image_async(self, log = False):
+        self.switch_page(self.FAST_IMAGE_GENERATING)
+        if log:
+            self.current_logfile = self.handler.get_logfile()
+            self.do_log(self.current_logfile)
+        # Build packages
+        _, all_recipes = self.recipe_model.get_selected_recipes()
+        self.set_user_config()
+        self.handler.reset_build()
+        self.handler.generate_packages(all_recipes, self.configuration.default_task)
+
+    def generate_image_async(self, cont = False):
+        self.switch_page(self.IMAGE_GENERATING)
+        self.handler.reset_build()
+        if not cont:
+            self.current_logfile = self.handler.get_logfile()
+            self.do_log(self.current_logfile)
+        # Build image
+        self.set_user_config()
+        toolchain_packages = []
+        base_image = None
+        if self.configuration.toolchain_build:
+            toolchain_packages = self.package_model.get_selected_packages_toolchain()
+        if self.configuration.selected_image == self.recipe_model.__custom_image__:
+            packages = self.package_model.get_selected_packages()
+            image = self.hob_image
+            base_image = self.configuration.initial_selected_image
+        else:
+            packages = []
+            image = self.configuration.selected_image
+        self.handler.generate_image(image,
+                                    base_image,
+                                    packages,
+                                    toolchain_packages,
+                                    self.configuration.default_task)
+
+    def generate_new_image(self, image, description):
+        base_image = self.configuration.initial_selected_image
+        if base_image == self.recipe_model.__custom_image__:
+            base_image = None
+        packages = self.package_model.get_selected_packages()
+        self.handler.generate_new_image(image, base_image, packages, description)
+
+    def ensure_dir(self, directory):
+        self.handler.ensure_dir(directory)
+
+    def get_parameters_sync(self):
+        return self.handler.get_parameters()
+
+    def request_package_info_async(self):
+        self.handler.request_package_info()
+
+    def cancel_build_sync(self, force=False):
+        self.handler.cancel_build(force)
+
+    def cancel_parse_sync(self):
+        self.handler.cancel_parse()
+
+    def switch_page(self, next_step):
+        # Main Workflow (Business Logic)
+        self.nb.set_current_page(self.__step2page__[next_step])
+
+        if next_step == self.MACHINE_SELECTION: # init step
+            self.image_configuration_page.show_machine()
+
+        elif next_step == self.RCPPKGINFO_POPULATING:
+            # MACHINE CHANGED action or SETTINGS CHANGED
+            # show the progress bar
+            self.image_configuration_page.show_info_populating()
+
+        elif next_step == self.RCPPKGINFO_POPULATED:
+            self.image_configuration_page.show_info_populated()
+
+        elif next_step == self.BASEIMG_SELECTED:
+            self.image_configuration_page.show_baseimg_selected()
+
+        elif next_step == self.RECIPE_SELECTION:
+            if self.recipe_model.get_selected_image() == self.recipe_model.__custom_image__:
+                self.recipe_details_page.set_recipe_curr_tab(self.recipe_details_page.ALL)
+            else:
+                self.recipe_details_page.set_recipe_curr_tab(self.recipe_details_page.INCLUDED)
+
+        elif next_step == self.PACKAGE_SELECTION:
+            self.configuration.initial_selected_packages = self.configuration.selected_packages
+            self.configuration.initial_user_selected_packages = self.configuration.user_selected_packages
+            self.package_details_page.set_title("Edit packages")
+            if self.recipe_model.get_selected_image() == self.recipe_model.__custom_image__:
+                self.package_details_page.set_packages_curr_tab(self.package_details_page.ALL)
+            else:
+                self.package_details_page.set_packages_curr_tab(self.package_details_page.INCLUDED)
+            self.package_details_page.show_page(self.current_logfile)
+
+
+        elif next_step == self.PACKAGE_GENERATING or next_step == self.FAST_IMAGE_GENERATING:
+            # both PACKAGE_GENERATING and FAST_IMAGE_GENERATING share the same page
+            self.build_details_page.show_page(next_step)
+
+        elif next_step == self.PACKAGE_GENERATED:
+            self.package_details_page.set_title("Step 2 of 2: Edit packages")
+            if self.recipe_model.get_selected_image() == self.recipe_model.__custom_image__:
+                self.package_details_page.set_packages_curr_tab(self.package_details_page.ALL)
+            else:
+                self.package_details_page.set_packages_curr_tab(self.package_details_page.INCLUDED)
+            self.package_details_page.show_page(self.current_logfile)
+
+        elif next_step == self.IMAGE_GENERATING:
+            # after packages are generated, selected_packages need to
+            # be updated in package_model per selected_image in recipe_model
+            self.build_details_page.show_page(next_step)
+
+        elif next_step == self.IMAGE_GENERATED:
+            self.image_details_page.show_page(next_step)
+
+        elif next_step == self.MY_IMAGE_OPENED:
+            self.image_details_page.show_page(next_step)
+
+        self.previous_step = self.current_step
+        self.current_step = next_step
+
+    def set_user_config_proxies(self):
+        if self.configuration.enable_proxy == True:
+            self.handler.set_http_proxy(self.configuration.combine_proxy("http"))
+            self.handler.set_https_proxy(self.configuration.combine_proxy("https"))
+            self.handler.set_ftp_proxy(self.configuration.combine_proxy("ftp"))
+            self.handler.set_socks_proxy(self.configuration.combine_proxy("socks"))
+            self.handler.set_cvs_proxy(self.configuration.combine_host_only("cvs"), self.configuration.combine_port_only("cvs"))
+        elif self.configuration.enable_proxy == False:
+            self.handler.set_http_proxy("")
+            self.handler.set_https_proxy("")
+            self.handler.set_ftp_proxy("")
+            self.handler.set_socks_proxy("")
+            self.handler.set_cvs_proxy("", "")
+
+    def set_user_config_extra(self):
+        self.handler.set_rootfs_size(self.configuration.image_rootfs_size)
+        self.handler.set_extra_size(self.configuration.image_extra_size)
+        self.handler.set_incompatible_license(self.configuration.incompat_license)
+        self.handler.set_sdk_machine(self.configuration.curr_sdk_machine)
+        self.handler.set_image_fstypes(self.configuration.image_fstypes)
+        self.handler.set_extra_config(self.configuration.extra_setting)
+        self.handler.set_extra_inherit("packageinfo image_types")
+        self.set_user_config_proxies()
+
+    def set_user_config(self):
+        # set bb layers
+        self.handler.set_bblayers(self.configuration.layers)
+        # set local configuration
+        self.handler.set_machine(self.configuration.curr_mach)
+        self.handler.set_package_format(self.configuration.curr_package_format)
+        self.handler.set_distro(self.configuration.curr_distro)
+        self.handler.set_dl_dir(self.configuration.dldir)
+        self.handler.set_sstate_dir(self.configuration.sstatedir)
+        self.handler.set_sstate_mirrors(self.configuration.sstatemirror)
+        self.handler.set_pmake(self.configuration.pmake)
+        self.handler.set_bbthreads(self.configuration.bbthread)
+        self.set_user_config_extra()
+
+    def update_recipe_model(self, selected_image, selected_recipes):
+        self.recipe_model.set_selected_image(selected_image)
+        self.recipe_model.set_selected_recipes(selected_recipes)
+
+    def update_package_model(self, selected_packages, user_selected_packages=None):
+        if user_selected_packages:
+            left = self.package_model.set_selected_packages(user_selected_packages, True)
+            self.configuration.user_selected_packages += left
+        left = self.package_model.set_selected_packages(selected_packages)
+        self.configuration.selected_packages += left
+
+    def update_configuration_parameters(self, params):
+        if params:
+            self.configuration.update(params)
+            self.parameters.update(params)
+
+    def set_base_image(self):
+        self.configuration.initial_selected_image = self.configuration.selected_image
+        if self.configuration.selected_image != self.recipe_model.__custom_image__:
+            self.hob_image = self.configuration.selected_image + "-edited"
+
+    def reset(self):
+        self.configuration.curr_mach = ""
+        self.configuration.clear_selection()
+        self.image_configuration_page.switch_machine_combo()
+        self.switch_page(self.MACHINE_SELECTION)
+
+    # Callback Functions
+    def handler_config_updated_cb(self, handler, which, values):
+        if which == "distro":
+            self.parameters.all_distros = values
+        elif which == "machine":
+            self.parameters.all_machines = values
+            self.image_configuration_page.update_machine_combo()
+        elif which == "machine-sdk":
+            self.parameters.all_sdk_machines = values
+
+    def handler_package_formats_updated_cb(self, handler, formats):
+        self.parameters.all_package_formats = formats
+
+    def switch_to_image_configuration_helper(self):
+        self.sanity_check_page.stop()
+        self.switch_page(self.IMAGE_CONFIGURATION)
+        self.image_configuration_page.switch_machine_combo()
+
+    def show_network_error_dialog_helper(self):
+        self.sanity_check_page.stop()
+        self.show_network_error_dialog()
+
+    def handler_command_succeeded_cb(self, handler, initcmd):
+        if initcmd == self.handler.GENERATE_CONFIGURATION:
+            if not self.configuration.curr_mach:
+                self.configuration.curr_mach = self.handler.runCommand(["getVariable", "HOB_MACHINE"]) or ""
+            self.update_configuration_parameters(self.get_parameters_sync())
+            if not self.sanity_checked:
+                self.sanity_check()
+                self.sanity_checked = True
+        elif initcmd == self.handler.SANITY_CHECK:
+            if self.had_network_error:
+                self.had_network_error = False
+                self.execute_after_sanity_check(self.show_network_error_dialog_helper)
+            else:
+                # Switch to the 'image configuration' page now, but we might need
+                # to wait for the minimum display time of the sanity check page
+                self.execute_after_sanity_check(self.switch_to_image_configuration_helper)
+        elif initcmd in [self.handler.GENERATE_RECIPES,
+                         self.handler.GENERATE_PACKAGES,
+                         self.handler.GENERATE_IMAGE]:
+            self.update_configuration_parameters(self.get_parameters_sync())
+            self.request_package_info_async()
+        elif initcmd == self.handler.POPULATE_PACKAGEINFO:
+            if self.current_step == self.RCPPKGINFO_POPULATING:
+                self.switch_page(self.RCPPKGINFO_POPULATED)
+                self.rcppkglist_populated()
+                return
+
+            self.rcppkglist_populated()
+            if self.current_step == self.FAST_IMAGE_GENERATING:
+                self.generate_image_async(True)
+
+    def show_error_dialog(self, msg):
+        lbl = "<b>Hob found an error</b>"
+        dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_ERROR, msg)
+        button = dialog.add_button("Close", gtk.RESPONSE_OK)
+        HobButton.style_button(button)
+        response = dialog.run()
+        dialog.destroy()
+
+    def show_warning_dialog(self):
+        dialog = ParsingWarningsDialog(title = "View warnings",
+                warnings = self.parsing_warnings,
+                parent = None,
+                flags = gtk.DIALOG_DESTROY_WITH_PARENT
+                       | gtk.DIALOG_NO_SEPARATOR)
+        response = dialog.run()
+        dialog.destroy()
+
+    def show_network_error_dialog(self):
+        lbl = "<b>Hob cannot connect to the network</b>"
+        msg = msg + "Please check your network connection. If you are using a proxy server, please make sure it is configured correctly."
+        dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_ERROR, msg)
+        button = dialog.add_button("Close", gtk.RESPONSE_OK)
+        HobButton.style_button(button)
+        button = dialog.add_button("Proxy settings", gtk.RESPONSE_CANCEL)
+        HobButton.style_button(button)
+        res = dialog.run()
+        dialog.destroy()
+        if res == gtk.RESPONSE_CANCEL:
+            res, settings_changed = self.show_simple_settings_dialog(SimpleSettingsDialog.PROXIES_PAGE_ID)
+            if not res:
+                return
+            if settings_changed:
+                self.reparse_post_adv_settings()
+
+    def handler_command_failed_cb(self, handler, msg):
+        if msg:
+            self.show_error_dialog(msg)
+        self.reset()
+
+    def handler_parsing_warning_cb(self, handler, warn_msg):
+        self.parsing_warnings.append(warn_msg)
+
+    def handler_sanity_failed_cb(self, handler, msg, network_error):
+        self.reset()
+        if network_error:
+            # Mark this in an internal field. The "network error" dialog will be
+            # shown later, when a SanityCheckPassed event will be handled
+            # (as sent by sanity.bbclass)
+            self.had_network_error = True
+        else:
+            msg = msg.replace("your local.conf", "Settings")
+            self.show_error_dialog(msg)
+            self.reset()
+
+    def window_sensitive(self, sensitive):
+        self.image_configuration_page.machine_combo.set_sensitive(sensitive)
+        self.image_configuration_page.machine_combo.child.set_sensitive(sensitive)
+        self.image_configuration_page.image_combo.set_sensitive(sensitive)
+        self.image_configuration_page.image_combo.child.set_sensitive(sensitive)
+        self.image_configuration_page.layer_button.set_sensitive(sensitive)
+        self.image_configuration_page.layer_info_icon.set_sensitive(sensitive)
+        self.image_configuration_page.toolbar.set_sensitive(sensitive)
+        self.image_configuration_page.view_adv_configuration_button.set_sensitive(sensitive)
+        self.image_configuration_page.config_build_button.set_sensitive(sensitive)
+
+        self.recipe_details_page.set_sensitive(sensitive)
+        self.package_details_page.set_sensitive(sensitive)
+        self.build_details_page.set_sensitive(sensitive)
+        self.image_details_page.set_sensitive(sensitive)
+
+        if sensitive:
+            self.window.set_cursor(None)
+        else:
+            self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+        self.sensitive = sensitive
+
+
+    def handler_generating_data_cb(self, handler):
+        self.window_sensitive(False)
+
+    def handler_data_generated_cb(self, handler):
+        self.window_sensitive(True)
+
+    def rcppkglist_populated(self):
+        selected_image = self.configuration.selected_image
+        selected_recipes = self.configuration.selected_recipes[:]
+        selected_packages = self.configuration.selected_packages[:]
+        user_selected_packages = self.configuration.user_selected_packages[:]
+
+        self.image_configuration_page.update_image_combo(self.recipe_model, selected_image)
+        self.image_configuration_page.update_image_desc()
+        self.update_recipe_model(selected_image, selected_recipes)
+        self.update_package_model(selected_packages, user_selected_packages)
+
+    def recipelist_changed_cb(self, recipe_model):
+        self.recipe_details_page.refresh_selection()
+
+    def packagelist_changed_cb(self, package_model):
+        self.package_details_page.refresh_selection()
+
+    def handler_recipe_populated_cb(self, handler):
+        self.image_configuration_page.update_progress_bar("Populating recipes", 0.99)
+
+    def handler_package_populated_cb(self, handler):
+        self.image_configuration_page.update_progress_bar("Populating packages", 1.0)
+
+    def handler_parsing_started_cb(self, handler, message):
+        if self.current_step != self.RCPPKGINFO_POPULATING:
+            return
+
+        fraction = 0
+        if message["eventname"] == "TreeDataPreparationStarted":
+            fraction = 0.6 + fraction
+            self.image_configuration_page.stop_button.set_sensitive(False)
+            self.image_configuration_page.update_progress_bar("Generating dependency tree", fraction)
+        else:
+            self.image_configuration_page.stop_button.set_sensitive(True)
+            self.image_configuration_page.update_progress_bar(message["title"], fraction)
+
+    def handler_parsing_cb(self, handler, message):
+        if self.current_step != self.RCPPKGINFO_POPULATING:
+            return
+
+        fraction = message["current"] * 1.0/message["total"]
+        if message["eventname"] == "TreeDataPreparationProgress":
+            fraction = 0.6 + 0.38 * fraction
+            self.image_configuration_page.update_progress_bar("Generating dependency tree", fraction)
+        else:
+            fraction = 0.6 * fraction
+            self.image_configuration_page.update_progress_bar(message["title"], fraction)
+
+    def handler_parsing_completed_cb(self, handler, message):
+        if self.current_step != self.RCPPKGINFO_POPULATING:
+            return
+
+        if message["eventname"] == "TreeDataPreparationCompleted":
+            fraction = 0.98
+        else:
+            fraction = 0.6
+        self.image_configuration_page.update_progress_bar("Generating dependency tree", fraction)
+
+    def handler_build_started_cb(self, running_build):
+        if self.current_step == self.FAST_IMAGE_GENERATING:
+            fraction = 0
+        elif self.current_step == self.IMAGE_GENERATING:
+            if self.previous_step == self.FAST_IMAGE_GENERATING:
+                fraction = 0.9
+            else:
+                fraction = 0
+        elif self.current_step == self.PACKAGE_GENERATING:
+            fraction = 0
+        self.build_details_page.update_progress_bar("Build Started: ", fraction)
+        self.build_details_page.show_configurations(self.configuration, self.parameters)
+
+    def build_succeeded(self):
+        if self.current_step == self.FAST_IMAGE_GENERATING:
+            fraction = 0.9
+        elif self.current_step == self.IMAGE_GENERATING:
+            fraction = 1.0
+            version = ""
+            self.parameters.image_names = []
+            selected_image = self.recipe_model.get_selected_image()
+            if selected_image == self.recipe_model.__custom_image__:
+                if self.configuration.initial_selected_image != selected_image:
+                    version = self.recipe_model.get_custom_image_version()
+                linkname = self.hob_image + version + "-" + self.configuration.curr_mach
+            else:
+                linkname = selected_image + '-' + self.configuration.curr_mach
+            image_extension = self.get_image_extension()
+            for image_type in self.parameters.image_types:
+                if image_type in image_extension:
+                    real_types = image_extension[image_type]
+                else:
+                    real_types = [image_type]
+                for real_image_type in real_types:
+                    linkpath = self.parameters.image_addr + '/' + linkname + '.' + real_image_type
+                    if os.path.exists(linkpath):
+                        self.parameters.image_names.append(os.readlink(linkpath))
+        elif self.current_step == self.PACKAGE_GENERATING:
+            fraction = 1.0
+        self.build_details_page.update_progress_bar("Build Completed: ", fraction)
+        self.handler.build_succeeded_async()
+        self.stopping = False
+
+        if self.current_step == self.PACKAGE_GENERATING:
+            self.switch_page(self.PACKAGE_GENERATED)
+        elif self.current_step == self.IMAGE_GENERATING:
+            self.switch_page(self.IMAGE_GENERATED)
+
+    def build_failed(self):
+        if self.stopping:
+            status = "stop"
+            message = "Build stopped: "
+            fraction = self.build_details_page.progress_bar.get_fraction()
+            stop_to_next_edit = ""
+            if self.current_step == self.FAST_IMAGE_GENERATING:
+                stop_to_next_edit = "image configuration"
+            elif self.current_step == self.IMAGE_GENERATING:
+                if self.previous_step == self.FAST_IMAGE_GENERATING:
+                    stop_to_next_edit = "image configuration"
+                else:
+                    stop_to_next_edit = "packages"
+            elif self.current_step == self.PACKAGE_GENERATING:
+                stop_to_next_edit = "recipes"
+            button = self.build_details_page.show_stop_page(stop_to_next_edit.split(' ')[0])
+            self.set_default(button)
+        else:
+            fail_to_next_edit = ""
+            if self.current_step == self.FAST_IMAGE_GENERATING:
+                fail_to_next_edit = "image configuration"
+                fraction = 0.9
+            elif self.current_step == self.IMAGE_GENERATING:
+                if self.previous_step == self.FAST_IMAGE_GENERATING:
+                    fail_to_next_edit = "image configuration"
+                else:
+                    fail_to_next_edit = "packages"
+                fraction = 1.0
+            elif self.current_step == self.PACKAGE_GENERATING:
+                fail_to_next_edit = "recipes"
+                fraction = 1.0
+            self.build_details_page.show_fail_page(fail_to_next_edit.split(' ')[0])
+            status = "fail"
+            message = "Build failed: "
+        self.build_details_page.update_progress_bar(message, fraction, status)
+        self.build_details_page.show_back_button()
+        self.build_details_page.hide_stop_button()
+        self.handler.build_failed_async()
+        self.stopping = False
+
+    def handler_build_succeeded_cb(self, running_build):
+        if not self.stopping:
+            self.build_succeeded()
+        else:
+            self.build_failed()
+
+
+    def handler_build_failed_cb(self, running_build):
+        self.build_failed()
+
+    def handler_build_aborted_cb(self, running_build):
+        self.build_failed()
+
+    def handler_no_provider_cb(self, running_build, msg):
+        dialog = CrumbsMessageDialog(self, glib.markup_escape_text(msg), gtk.MESSAGE_INFO)
+        button = dialog.add_button("Close", gtk.RESPONSE_OK)
+        HobButton.style_button(button)
+        dialog.run()
+        dialog.destroy()
+        self.build_failed()
+
+    def handler_task_started_cb(self, running_build, message): 
+        fraction = message["current"] * 1.0/message["total"]
+        title = "Build packages"
+        if self.current_step == self.FAST_IMAGE_GENERATING:
+            if message["eventname"] == "sceneQueueTaskStarted":
+                fraction = 0.27 * fraction
+            elif message["eventname"] == "runQueueTaskStarted":
+                fraction = 0.27 + 0.63 * fraction
+        elif self.current_step == self.IMAGE_GENERATING:
+            title = "Build image"
+            if self.previous_step == self.FAST_IMAGE_GENERATING:
+                if message["eventname"] == "sceneQueueTaskStarted":
+                    fraction = 0.27 + 0.63 + 0.03 * fraction
+                elif message["eventname"] == "runQueueTaskStarted":
+                    fraction = 0.27 + 0.63 + 0.03 + 0.07 * fraction
+            else:
+                if message["eventname"] == "sceneQueueTaskStarted":
+                    fraction = 0.2 * fraction
+                elif message["eventname"] == "runQueueTaskStarted":
+                    fraction = 0.2 + 0.8 * fraction
+        elif self.current_step == self.PACKAGE_GENERATING:
+            if message["eventname"] == "sceneQueueTaskStarted":
+                fraction = 0.2 * fraction
+            elif message["eventname"] == "runQueueTaskStarted":
+                fraction = 0.2 + 0.8 * fraction
+        self.build_details_page.update_progress_bar(title + ": ", fraction)
+        self.build_details_page.update_build_status(message["current"], message["total"], message["task"])
+
+    def handler_disk_full_cb(self, running_build):
+        self.disk_full = True
+
+    def handler_build_failure_cb(self, running_build):
+        self.build_details_page.show_issues()
+
+    def handler_build_log_cb(self, running_build, func, obj):
+        if hasattr(self.logger, func):
+            getattr(self.logger, func)(obj)
+
+    def destroy_window_cb(self, widget, event):
+        if not self.sensitive:
+            return True
+        elif self.handler.building:
+            self.stop_build()
+            return True
+        else:
+            gtk.main_quit()
+
+    def event_handle_SIGINT(self, signal, frame):
+        for w in gtk.window_list_toplevels():
+            if w.get_modal():
+                w.response(gtk.RESPONSE_DELETE_EVENT)
+        sys.exit(0)
+
+    def build_packages(self):
+        _, all_recipes = self.recipe_model.get_selected_recipes()
+        if not all_recipes:
+            lbl = "<b>No selections made</b>"
+            msg = "You have not made any selections"
+            msg = msg + " so there isn't anything to bake at this time."
+            dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO, msg)
+            button = dialog.add_button("Close", gtk.RESPONSE_OK)
+            HobButton.style_button(button)
+            dialog.run()
+            dialog.destroy()
+            return
+        self.generate_packages_async(True)
+
+    def build_image(self):
+        selected_packages = self.package_model.get_selected_packages()
+        if not selected_packages:      
+            lbl = "<b>No selections made</b>"
+            msg = "You have not made any selections"
+            msg = msg + " so there isn't anything to bake at this time."
+            dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO, msg)
+            button = dialog.add_button("Close", gtk.RESPONSE_OK)
+            HobButton.style_button(button)
+            dialog.run()
+            dialog.destroy()
+            return
+        self.generate_image_async(True)
+
+    def just_bake(self):
+        selected_image = self.recipe_model.get_selected_image()
+        selected_packages = self.package_model.get_selected_packages() or []
+
+        # If no base image and no selected packages don't build anything
+        if not (selected_packages or selected_image != self.recipe_model.__custom_image__):
+            lbl = "<b>No selections made</b>"
+            msg = "You have not made any selections"
+            msg = msg + " so there isn't anything to bake at this time."
+            dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO, msg)
+            button = dialog.add_button("Close", gtk.RESPONSE_OK)
+            HobButton.style_button(button)
+            dialog.run()
+            dialog.destroy()
+            return
+
+        self.fast_generate_image_async(True)
+
+    def show_recipe_property_dialog(self, properties):
+        information = {}
+        dialog = PropertyDialog(title = properties["name"] +' '+ "properties",
+                      parent = self,
+                      information = properties,
+                      flags = gtk.DIALOG_DESTROY_WITH_PARENT
+                          | gtk.DIALOG_NO_SEPARATOR)
+
+        dialog.set_modal(False)
+
+        button = dialog.add_button("Close", gtk.RESPONSE_NO)
+        HobAltButton.style_button(button)
+        button.connect("clicked", lambda w: dialog.destroy())
+
+        dialog.run()
+
+    def show_packages_property_dialog(self, properties):
+        information = {}
+        dialog = PropertyDialog(title = properties["name"] +' '+ "properties",
+                      parent = self,
+                      information = properties,
+                      flags = gtk.DIALOG_DESTROY_WITH_PARENT
+                          | gtk.DIALOG_NO_SEPARATOR)
+
+        dialog.set_modal(False)
+
+        button = dialog.add_button("Close", gtk.RESPONSE_NO)
+        HobAltButton.style_button(button)
+        button.connect("clicked", lambda w: dialog.destroy())
+
+        dialog.run()
+
+    def show_layer_selection_dialog(self):
+        dialog = LayerSelectionDialog(title = "Layers",
+                     layers = copy.deepcopy(self.configuration.layers),
+                     layers_non_removable = copy.deepcopy(self.configuration.layers_non_removable),
+                     all_layers = self.parameters.all_layers,
+                     parent = self,
+                     flags = gtk.DIALOG_MODAL
+                         | gtk.DIALOG_DESTROY_WITH_PARENT
+                         | gtk.DIALOG_NO_SEPARATOR)
+        button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
+        HobAltButton.style_button(button)
+        button = dialog.add_button("OK", gtk.RESPONSE_YES)
+        HobButton.style_button(button)
+        response = dialog.run()
+        if response == gtk.RESPONSE_YES:
+            self.configuration.layers = dialog.layers
+            # DO refresh layers
+            if dialog.layers_changed:
+                self.update_config_async()
+        dialog.destroy()
+
+    def get_image_extension(self):
+        image_extension = {}
+        for type in self.parameters.image_types:
+            ext = self.handler.runCommand(["getVariable", "IMAGE_EXTENSION_%s" % type])
+            if ext:
+                image_extension[type] = ext.split(' ')
+
+        return image_extension
+
+    def show_load_my_images_dialog(self):
+        image_extension = self.get_image_extension()
+        dialog = ImageSelectionDialog(self.parameters.image_addr, self.parameters.image_types,
+                                      "Open My Images", self,
+                                       gtk.FILE_CHOOSER_ACTION_SAVE, None,
+                                       image_extension)
+        button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
+        HobAltButton.style_button(button)
+        button = dialog.add_button("Open", gtk.RESPONSE_YES)
+        HobButton.style_button(button)
+        response = dialog.run()
+        if response == gtk.RESPONSE_YES:
+            if not dialog.image_names:
+                lbl = "<b>No selections made</b>"
+                msg = "You have not made any selections"
+                crumbs_dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO, msg)
+                button = crumbs_dialog.add_button("Close", gtk.RESPONSE_OK)
+                HobButton.style_button(button)
+                crumbs_dialog.run()
+                crumbs_dialog.destroy()
+                dialog.destroy()
+                return
+
+            self.parameters.image_addr = dialog.image_folder
+            self.parameters.image_names = dialog.image_names[:]
+            self.switch_page(self.MY_IMAGE_OPENED)
+
+        dialog.destroy()
+
+    def show_adv_settings_dialog(self, tab=None):
+        dialog = AdvancedSettingsDialog(title = "Advanced configuration",
+            configuration = copy.deepcopy(self.configuration),
+            all_image_types = self.parameters.image_types,
+            all_package_formats = self.parameters.all_package_formats,
+            all_distros = self.parameters.all_distros,
+            all_sdk_machines = self.parameters.all_sdk_machines,
+            max_threads = self.parameters.max_threads,
+            parent = self,
+            flags = gtk.DIALOG_MODAL
+                    | gtk.DIALOG_DESTROY_WITH_PARENT
+                    | gtk.DIALOG_NO_SEPARATOR)
+        button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
+        HobAltButton.style_button(button)
+        button = dialog.add_button("Save", gtk.RESPONSE_YES)
+        HobButton.style_button(button)
+        dialog.set_save_button(button)
+        response = dialog.run()
+        settings_changed = False
+        if response == gtk.RESPONSE_YES:
+            self.configuration = dialog.configuration
+            self.configuration.save(self.handler, True) # remember settings
+            settings_changed = dialog.settings_changed
+        dialog.destroy()
+        return response == gtk.RESPONSE_YES, settings_changed
+
+    def show_simple_settings_dialog(self, tab=None):
+        dialog = SimpleSettingsDialog(title = "Settings",
+            configuration = copy.deepcopy(self.configuration),
+            all_image_types = self.parameters.image_types,
+            all_package_formats = self.parameters.all_package_formats,
+            all_distros = self.parameters.all_distros,
+            all_sdk_machines = self.parameters.all_sdk_machines,
+            max_threads = self.parameters.max_threads,
+            parent = self,
+            flags = gtk.DIALOG_MODAL
+                    | gtk.DIALOG_DESTROY_WITH_PARENT
+                    | gtk.DIALOG_NO_SEPARATOR,
+            handler = self.handler)
+        button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
+        HobAltButton.style_button(button)
+        button = dialog.add_button("Save", gtk.RESPONSE_YES)
+        HobButton.style_button(button)
+        if tab:
+            dialog.switch_to_page(tab)
+        response = dialog.run()
+        settings_changed = False
+        if response == gtk.RESPONSE_YES:
+            self.configuration = dialog.configuration
+            self.configuration.save(self.handler, True) # remember settings
+            settings_changed = dialog.settings_changed
+            if dialog.proxy_settings_changed:
+                self.set_user_config_proxies()
+        elif dialog.proxy_test_ran:
+            # The user might have modified the proxies in the "Proxy"
+            # tab, which in turn made the proxy settings modify in bb.
+            # If "Cancel" was pressed, restore the previous proxy
+            # settings inside bb.
+            self.set_user_config_proxies()
+        dialog.destroy()
+        return response == gtk.RESPONSE_YES, settings_changed
+
+    def reparse_post_adv_settings(self):
+        if not self.configuration.curr_mach:
+            self.update_config_async()
+        else:
+            self.configuration.clear_selection()
+            # DO reparse recipes
+            self.populate_recipe_package_info_async()
+
+    def deploy_image(self, image_name):
+        if not image_name:
+            lbl = "<b>Please select an image to deploy.</b>"
+            dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO)
+            button = dialog.add_button("Close", gtk.RESPONSE_OK)
+            HobButton.style_button(button)
+            dialog.run()
+            dialog.destroy()
+            return
+
+        image_path = os.path.join(self.parameters.image_addr, image_name)
+        dialog = DeployImageDialog(title = "Usb Image Maker",
+            image_path = image_path,
+            parent = self,
+            flags = gtk.DIALOG_MODAL
+                    | gtk.DIALOG_DESTROY_WITH_PARENT
+                    | gtk.DIALOG_NO_SEPARATOR)
+        button = dialog.add_button("Close", gtk.RESPONSE_NO)
+        HobAltButton.style_button(button)
+        button = dialog.add_button("Make usb image", gtk.RESPONSE_YES)
+        HobButton.style_button(button)
+        response = dialog.run()
+        dialog.destroy()
+
+    def show_load_kernel_dialog(self):
+        dialog = gtk.FileChooserDialog("Load Kernel Files", self,
+                                       gtk.FILE_CHOOSER_ACTION_SAVE)
+        button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
+        HobAltButton.style_button(button)
+        button = dialog.add_button("Open", gtk.RESPONSE_YES)
+        HobButton.style_button(button)
+        filter = gtk.FileFilter()
+        filter.set_name("Kernel Files")
+        filter.add_pattern("*.bin")
+        dialog.add_filter(filter)
+
+        dialog.set_current_folder(self.parameters.image_addr)
+
+        response = dialog.run()
+        kernel_path = ""
+        if response == gtk.RESPONSE_YES:
+            kernel_path = dialog.get_filename()
+
+        dialog.destroy()
+
+        return kernel_path
+
+    def runqemu_image(self, image_name, kernel_name):
+        if not image_name or not kernel_name:
+            lbl = "<b>Please select %s to launch in QEMU.</b>" % ("a kernel" if image_name else "an image")
+            dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO)
+            button = dialog.add_button("Close", gtk.RESPONSE_OK)
+            HobButton.style_button(button)
+            dialog.run()
+            dialog.destroy()
+            return
+
+        kernel_path = os.path.join(self.parameters.image_addr, kernel_name)
+        image_path = os.path.join(self.parameters.image_addr, image_name)
+
+        source_env_path = os.path.join(self.parameters.core_base, "oe-init-build-env")
+        tmp_path = self.parameters.tmpdir
+        cmdline = bb.ui.crumbs.utils.which_terminal()
+        if os.path.exists(image_path) and os.path.exists(kernel_path) \
+           and os.path.exists(source_env_path) and os.path.exists(tmp_path) \
+           and cmdline:
+            cmdline += "\' bash -c \"export OE_TMPDIR=" + tmp_path + "; "
+            cmdline += "source " + source_env_path + " " + os.getcwd() + "; "
+            cmdline += "runqemu " + kernel_path + " " + image_path + "\"\'"
+            subprocess.Popen(shlex.split(cmdline))
+        else:
+            lbl = "<b>Path error</b>"
+            msg = "One of your paths is wrong,"
+            msg = msg + " please make sure the following paths exist:\n"
+            msg = msg + "image path:" + image_path + "\n"
+            msg = msg + "kernel path:" + kernel_path + "\n"
+            msg = msg + "source environment path:" + source_env_path + "\n"
+            msg = msg + "tmp path: " + tmp_path + "."
+            msg = msg + "You may be missing either xterm or vte for terminal services."
+            dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_ERROR, msg)
+            button = dialog.add_button("Close", gtk.RESPONSE_OK)
+            HobButton.style_button(button)
+            dialog.run()
+            dialog.destroy()
+
+    def show_packages(self):
+        self.package_details_page.refresh_tables()
+        self.switch_page(self.PACKAGE_SELECTION)
+
+    def show_recipes(self):
+        self.switch_page(self.RECIPE_SELECTION)
+
+    def show_image_details(self):
+        self.switch_page(self.IMAGE_GENERATED)
+
+    def show_configuration(self):
+        self.switch_page(self.BASEIMG_SELECTED)
+
+    def stop_build(self):
+        if self.stopping:
+            lbl = "<b>Force Stop build?</b>"
+            msg = "You've already selected Stop once,"
+            msg = msg + " would you like to 'Force Stop' the build?\n\n"
+            msg = msg + "This will stop the build as quickly as possible but may"
+            msg = msg + " well leave your build directory in an  unusable state"
+            msg = msg + " that requires manual steps to fix."
+            dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_WARNING, msg)
+            button = dialog.add_button("Cancel", gtk.RESPONSE_CANCEL)
+            HobAltButton.style_button(button)
+            button = dialog.add_button("Force Stop", gtk.RESPONSE_YES)
+            HobButton.style_button(button)
+        else:
+            lbl = "<b>Stop build?</b>"
+            msg = "Are you sure you want to stop this"
+            msg = msg + " build?\n\n'Stop' will stop the build as soon as all in"
+            msg = msg + " progress build tasks are finished. However if a"
+            msg = msg + " lengthy compilation phase is in progress this may take"
+            msg = msg + " some time.\n\n"
+            msg = msg + "'Force Stop' will stop the build as quickly as"
+            msg = msg + " possible but may well leave your build directory in an"
+            msg = msg + " unusable state that requires manual steps to fix."
+            dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_WARNING, msg)
+            button = dialog.add_button("Cancel", gtk.RESPONSE_CANCEL)
+            HobAltButton.style_button(button)
+            button = dialog.add_button("Force stop", gtk.RESPONSE_YES)
+            HobAltButton.style_button(button)
+            button = dialog.add_button("Stop", gtk.RESPONSE_OK)
+            HobButton.style_button(button)
+        response = dialog.run()
+        dialog.destroy()
+        if response != gtk.RESPONSE_CANCEL:
+            self.stopping = True
+        if response == gtk.RESPONSE_OK:
+            self.build_details_page.progress_bar.set_stop_title("Stopping the build....")
+            self.build_details_page.progress_bar.set_rcstyle("stop")
+            self.cancel_build_sync()
+        elif response == gtk.RESPONSE_YES:
+            self.cancel_build_sync(True)
+
+    def do_log(self, consolelogfile = None):
+        if consolelogfile:
+            bb.utils.mkdirhier(os.path.dirname(consolelogfile))
+            if self.consolelog:
+                self.logger.removeHandler(self.consolelog)
+                self.consolelog = None
+            self.consolelog = logging.FileHandler(consolelogfile)
+            bb.msg.addDefaultlogFilter(self.consolelog)
+            format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
+            self.consolelog.setFormatter(format)
+
+            self.logger.addHandler(self.consolelog)
+
+    def get_topdir(self):
+        return self.handler.get_topdir()
+
+    def wait(self, delay):
+        time_start = time.time()
+        time_end = time_start + delay
+        while time_end > time.time():
+            while gtk.events_pending():
+                gtk.main_iteration()
diff --git a/bitbake/lib/bb/ui/crumbs/buildmanager.py b/bitbake/lib/bb/ui/crumbs/buildmanager.py
new file mode 100644
index 0000000..e858d75
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/buildmanager.py
@@ -0,0 +1,455 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2008        Intel Corporation
+#
+# Authored by Rob Bradford <rob@linux.intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import gobject
+import threading
+import os
+import datetime
+import time
+
+class BuildConfiguration:
+    """ Represents a potential *or* historic *or* concrete build. It
+    encompasses all the things that we need to tell bitbake to do to make it
+    build what we want it to build.
+
+    It also stored the metadata URL and the set of possible machines (and the
+    distros / images / uris for these. Apart from the metdata URL these are
+    not serialised to file (since they may be transient). In some ways this
+    functionality might be shifted to the loader class."""
+
+    def __init__ (self):
+        self.metadata_url = None
+
+        # Tuple of (distros, image, urls)
+        self.machine_options = {}
+
+        self.machine = None
+        self.distro = None
+        self.image = None
+        self.urls = []
+        self.extra_urls = []
+        self.extra_pkgs = []
+
+    def get_machines_model (self):
+        model = gtk.ListStore (gobject.TYPE_STRING)
+        for machine in self.machine_options.keys():
+            model.append ([machine])
+
+        return model
+
+    def get_distro_and_images_models (self, machine):
+        distro_model = gtk.ListStore (gobject.TYPE_STRING)
+
+        for distro in self.machine_options[machine][0]:
+            distro_model.append ([distro])
+
+        image_model = gtk.ListStore (gobject.TYPE_STRING)
+
+        for image in self.machine_options[machine][1]:
+            image_model.append ([image])
+
+        return (distro_model, image_model)
+
+    def get_repos (self):
+        self.urls = self.machine_options[self.machine][2]
+        return self.urls
+
+    # It might be a lot lot better if we stored these in like, bitbake conf
+    # file format.
+    @staticmethod
+    def load_from_file (filename):
+
+        conf = BuildConfiguration()
+        with open(filename, "r") as f:
+            for line in f:
+                data = line.split (";")[1]
+                if (line.startswith ("metadata-url;")):
+                    conf.metadata_url = data.strip()
+                    continue
+                if (line.startswith ("url;")):
+                    conf.urls += [data.strip()]
+                    continue
+                if (line.startswith ("extra-url;")):
+                    conf.extra_urls += [data.strip()]
+                    continue
+                if (line.startswith ("machine;")):
+                    conf.machine = data.strip()
+                    continue
+                if (line.startswith ("distribution;")):
+                    conf.distro = data.strip()
+                    continue
+                if (line.startswith ("image;")):
+                    conf.image = data.strip()
+                    continue
+
+        return conf
+
+    # Serialise to a file. This is part of the build process and we use this
+    # to be able to repeat a given build (using the same set of parameters)
+    # but also so that we can include the details of the image / machine /
+    # distro in the build manager tree view.
+    def write_to_file (self, filename):
+        f = open (filename, "w")
+
+        lines = []
+
+        if (self.metadata_url):
+            lines += ["metadata-url;%s\n" % (self.metadata_url)]
+
+        for url in self.urls:
+            lines += ["url;%s\n" % (url)]
+
+        for url in self.extra_urls:
+            lines += ["extra-url;%s\n" % (url)]
+
+        if (self.machine):
+            lines += ["machine;%s\n" % (self.machine)]
+
+        if (self.distro):
+            lines += ["distribution;%s\n" % (self.distro)]
+
+        if (self.image):
+            lines += ["image;%s\n" % (self.image)]
+
+        f.writelines (lines)
+        f.close ()
+
+class BuildResult(gobject.GObject):
+    """ Represents an historic build. Perhaps not successful. But it includes
+    things such as the files that are in the directory (the output from the
+    build) as well as a deserialised BuildConfiguration file that is stored in
+    ".conf" in the directory for the build.
+
+    This is GObject so that it can be included in the TreeStore."""
+
+    (STATE_COMPLETE, STATE_FAILED, STATE_ONGOING) = \
+        (0, 1, 2)
+
+    def __init__ (self, parent, identifier):
+        gobject.GObject.__init__ (self)
+        self.date = None
+
+        self.files = []
+        self.status = None
+        self.identifier = identifier
+        self.path = os.path.join (parent, identifier)
+
+        # Extract the date, since the directory name is of the
+        # format build-<year><month><day>-<ordinal> we can easily
+        # pull it out.
+        # TODO: Better to stat a file?
+        (_, date, revision) = identifier.split ("-")
+        print(date)
+
+        year = int (date[0:4])
+        month = int (date[4:6])
+        day = int (date[6:8])
+
+        self.date = datetime.date (year, month, day)
+
+        self.conf = None
+
+        # By default builds are STATE_FAILED unless we find a "complete" file
+        # in which case they are STATE_COMPLETE
+        self.state = BuildResult.STATE_FAILED
+        for file in os.listdir (self.path):
+            if (file.startswith (".conf")):
+                conffile = os.path.join (self.path, file)
+                self.conf = BuildConfiguration.load_from_file (conffile)
+            elif (file.startswith ("complete")):
+                self.state = BuildResult.STATE_COMPLETE
+            else:
+                self.add_file (file)
+
+    def add_file (self, file):
+        # Just add the file for now. Don't care about the type.
+        self.files += [(file, None)]
+
+class BuildManagerModel (gtk.TreeStore):
+    """ Model for the BuildManagerTreeView. This derives from gtk.TreeStore
+    but it abstracts nicely what the columns mean and the setup of the columns
+    in the model. """
+
+    (COL_IDENT, COL_DESC, COL_MACHINE, COL_DISTRO, COL_BUILD_RESULT, COL_DATE, COL_STATE) = \
+        (0, 1, 2, 3, 4, 5, 6)
+
+    def __init__ (self):
+        gtk.TreeStore.__init__ (self,
+            gobject.TYPE_STRING,
+            gobject.TYPE_STRING,
+            gobject.TYPE_STRING,
+            gobject.TYPE_STRING,
+            gobject.TYPE_OBJECT,
+            gobject.TYPE_INT64,
+            gobject.TYPE_INT)
+
+class BuildManager (gobject.GObject):
+    """ This class manages the historic builds that have been found in the
+    "results" directory but is also used for starting a new build."""
+
+    __gsignals__ = {
+        'population-finished' : (gobject.SIGNAL_RUN_LAST,
+                                 gobject.TYPE_NONE,
+                                 ()),
+        'populate-error' : (gobject.SIGNAL_RUN_LAST,
+                            gobject.TYPE_NONE,
+                            ())
+    }
+
+    def update_build_result (self, result, iter):
+        # Convert the date into something we can sort by.
+        date = long (time.mktime (result.date.timetuple()))
+
+        # Add a top level entry for the build
+
+        self.model.set (iter,
+            BuildManagerModel.COL_IDENT, result.identifier,
+            BuildManagerModel.COL_DESC, result.conf.image,
+            BuildManagerModel.COL_MACHINE, result.conf.machine,
+            BuildManagerModel.COL_DISTRO, result.conf.distro,
+            BuildManagerModel.COL_BUILD_RESULT, result,
+            BuildManagerModel.COL_DATE, date,
+            BuildManagerModel.COL_STATE, result.state)
+
+        # And then we use the files in the directory as the children for the
+        # top level iter.
+        for file in result.files:
+            self.model.append (iter, (None, file[0], None, None, None, date, -1))
+
+    # This function is called as an idle by the BuildManagerPopulaterThread
+    def add_build_result (self, result):
+        gtk.gdk.threads_enter()
+        self.known_builds += [result]
+
+        self.update_build_result (result, self.model.append (None))
+
+        gtk.gdk.threads_leave()
+
+    def notify_build_finished (self):
+        # This is a bit of a hack. If we have a running build running then we
+        # will have a row in the model in STATE_ONGOING. Find it and make it
+        # as if it was a proper historic build (well, it is completed now....)
+
+        # We need to use the iters here rather than the Python iterator
+        # interface to the model since we need to pass it into
+        # update_build_result
+
+        iter = self.model.get_iter_first()
+
+        while (iter):
+            (ident, state) = self.model.get(iter,
+                BuildManagerModel.COL_IDENT,
+                BuildManagerModel.COL_STATE)
+
+            if state == BuildResult.STATE_ONGOING:
+                result = BuildResult (self.results_directory, ident)
+                self.update_build_result (result, iter)
+            iter = self.model.iter_next(iter)
+
+    def notify_build_succeeded (self):
+        # Write the "complete" file so that when we create the BuildResult
+        # object we put into the model
+
+        complete_file_path = os.path.join (self.cur_build_directory, "complete")
+        f = file (complete_file_path, "w")
+        f.close()
+        self.notify_build_finished()
+
+    def notify_build_failed (self):
+        # Without a "complete" file then this will mark the build as failed:
+        self.notify_build_finished()
+
+    # This function is called as an idle
+    def emit_population_finished_signal (self):
+        gtk.gdk.threads_enter()
+        self.emit ("population-finished")
+        gtk.gdk.threads_leave()
+
+    class BuildManagerPopulaterThread (threading.Thread):
+        def __init__ (self, manager, directory):
+            threading.Thread.__init__ (self)
+            self.manager = manager
+            self.directory = directory
+
+        def run (self):
+            # For each of the "build-<...>" directories ..
+
+            if os.path.exists (self.directory):
+                for directory in os.listdir (self.directory):
+
+                    if not directory.startswith ("build-"):
+                        continue
+
+                    build_result = BuildResult (self.directory, directory)
+                    self.manager.add_build_result (build_result)
+
+            gobject.idle_add (BuildManager.emit_population_finished_signal,
+                self.manager)
+
+    def __init__ (self, server, results_directory):
+        gobject.GObject.__init__ (self)
+
+        # The builds that we've found from walking the result directory
+        self.known_builds = []
+
+        # Save out the bitbake server, we need this for issuing commands to
+        # the cooker:
+        self.server = server
+
+        # The TreeStore that we use
+        self.model = BuildManagerModel ()
+
+        # The results directory is where we create (and look for) the
+        # build-<xyz>-<n> directories. We need to populate ourselves from
+        # directory
+        self.results_directory = results_directory
+        self.populate_from_directory (self.results_directory)
+
+    def populate_from_directory (self, directory):
+        thread = BuildManager.BuildManagerPopulaterThread (self, directory)
+        thread.start()
+
+    # Come up with the name for the next build ident by combining "build-"
+    # with the date formatted as yyyymmdd and then an ordinal. We do this by
+    # an optimistic algorithm incrementing the ordinal if we find that it
+    # already exists.
+    def get_next_build_ident (self):
+        today = datetime.date.today ()
+        datestr = str (today.year) + str (today.month) + str (today.day)
+
+        revision = 0
+        test_name = "build-%s-%d" % (datestr, revision)
+        test_path = os.path.join (self.results_directory, test_name)
+
+        while (os.path.exists (test_path)):
+            revision += 1
+            test_name = "build-%s-%d" % (datestr, revision)
+            test_path = os.path.join (self.results_directory, test_name)
+
+        return test_name
+
+    # Take a BuildConfiguration and then try and build it based on the
+    # parameters of that configuration. S
+    def do_build (self, conf):
+        server = self.server
+
+        # Work out the build directory. Note we actually create the
+        # directories here since we need to write the ".conf" file. Otherwise
+        # we could have relied on bitbake's builder thread to actually make
+        # the directories as it proceeds with the build.
+        ident = self.get_next_build_ident ()
+        build_directory = os.path.join (self.results_directory,
+            ident)
+        self.cur_build_directory = build_directory
+        os.makedirs (build_directory)
+
+        conffile = os.path.join (build_directory, ".conf")
+        conf.write_to_file (conffile)
+
+        # Add a row to the model representing this ongoing build. It's kinda a
+        # fake entry. If this build completes or fails then this gets updated
+        # with the real stuff like the historic builds
+        date = long (time.time())
+        self.model.append (None, (ident, conf.image, conf.machine, conf.distro,
+            None, date, BuildResult.STATE_ONGOING))
+        try:
+            server.runCommand(["setVariable", "BUILD_IMAGES_FROM_FEEDS", 1])
+            server.runCommand(["setVariable", "MACHINE", conf.machine])
+            server.runCommand(["setVariable", "DISTRO", conf.distro])
+            server.runCommand(["setVariable", "PACKAGE_CLASSES", "package_ipk"])
+            server.runCommand(["setVariable", "BBFILES", \
+              """${OEROOT}/meta/packages/*/*.bb ${OEROOT}/meta-moblin/packages/*/*.bb"""])
+            server.runCommand(["setVariable", "TMPDIR", "${OEROOT}/build/tmp"])
+            server.runCommand(["setVariable", "IPK_FEED_URIS", \
+                " ".join(conf.get_repos())])
+            server.runCommand(["setVariable", "DEPLOY_DIR_IMAGE",
+                build_directory])
+            server.runCommand(["buildTargets", [conf.image], "rootfs"])
+
+        except Exception as e:
+            print(e)
+
+class BuildManagerTreeView (gtk.TreeView):
+    """ The tree view for the build manager. This shows the historic builds
+    and so forth. """
+
+    # We use this function to control what goes in the cell since we store
+    # the date in the model as seconds since the epoch (for sorting) and so we
+    # need to make it human readable.
+    def date_format_custom_cell_data_func (self, col, cell, model, iter):
+        date = model.get (iter, BuildManagerModel.COL_DATE)[0]
+        datestr = time.strftime("%A %d %B %Y", time.localtime(date))
+        cell.set_property ("text", datestr)
+
+    # This format function controls what goes in the cell. We use this to map
+    # the integer state to a string and also to colourise the text
+    def state_format_custom_cell_data_fun (self, col, cell, model, iter):
+        state = model.get (iter, BuildManagerModel.COL_STATE)[0]
+
+        if (state == BuildResult.STATE_ONGOING):
+            cell.set_property ("text", "Active")
+            cell.set_property ("foreground", "#000000")
+        elif (state == BuildResult.STATE_FAILED):
+            cell.set_property ("text", "Failed")
+            cell.set_property ("foreground", "#ff0000")
+        elif (state == BuildResult.STATE_COMPLETE):
+            cell.set_property ("text", "Complete")
+            cell.set_property ("foreground", "#00ff00")
+        else:
+            cell.set_property ("text", "")
+
+    def __init__ (self):
+        gtk.TreeView.__init__(self)
+
+        # Misc descriptiony thing
+        renderer = gtk.CellRendererText ()
+        col = gtk.TreeViewColumn (None, renderer,
+            text=BuildManagerModel.COL_DESC)
+        self.append_column (col)
+
+        # Machine
+        renderer = gtk.CellRendererText ()
+        col = gtk.TreeViewColumn ("Machine", renderer,
+            text=BuildManagerModel.COL_MACHINE)
+        self.append_column (col)
+
+        # distro
+        renderer = gtk.CellRendererText ()
+        col = gtk.TreeViewColumn ("Distribution", renderer,
+            text=BuildManagerModel.COL_DISTRO)
+        self.append_column (col)
+
+        # date (using a custom function for formatting the cell contents it
+        # takes epoch -> human readable string)
+        renderer = gtk.CellRendererText ()
+        col = gtk.TreeViewColumn ("Date", renderer,
+            text=BuildManagerModel.COL_DATE)
+        self.append_column (col)
+        col.set_cell_data_func (renderer,
+            self.date_format_custom_cell_data_func)
+
+        # For status.
+        renderer = gtk.CellRendererText ()
+        col = gtk.TreeViewColumn ("Status", renderer,
+            text = BuildManagerModel.COL_STATE)
+        self.append_column (col)
+        col.set_cell_data_func (renderer,
+            self.state_format_custom_cell_data_fun)
diff --git a/bitbake/lib/bb/ui/crumbs/hig/__init__.py b/bitbake/lib/bb/ui/crumbs/hig/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/__init__.py
diff --git a/bitbake/lib/bb/ui/crumbs/hig/advancedsettingsdialog.py b/bitbake/lib/bb/ui/crumbs/hig/advancedsettingsdialog.py
new file mode 100644
index 0000000..e0b3553
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/advancedsettingsdialog.py
@@ -0,0 +1,341 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012   Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import hashlib
+from bb.ui.crumbs.hobwidget import HobInfoButton, HobButton
+from bb.ui.crumbs.progressbar import HobProgressBar
+from bb.ui.crumbs.hig.settingsuihelper import SettingsUIHelper
+from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
+from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog
+from bb.ui.crumbs.hig.proxydetailsdialog import ProxyDetailsDialog
+
+"""
+The following are convenience classes for implementing GNOME HIG compliant
+BitBake GUI's
+In summary: spacing = 12px, border-width = 6px
+"""
+
+class AdvancedSettingsDialog (CrumbsDialog, SettingsUIHelper):
+    
+    def details_cb(self, button, parent, protocol):
+        dialog = ProxyDetailsDialog(title = protocol.upper() + " Proxy Details",
+            user = self.configuration.proxies[protocol][1],
+            passwd = self.configuration.proxies[protocol][2],
+            parent = parent,
+            flags = gtk.DIALOG_MODAL
+                    | gtk.DIALOG_DESTROY_WITH_PARENT
+                    | gtk.DIALOG_NO_SEPARATOR)
+        dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK)
+        response = dialog.run()
+        if response == gtk.RESPONSE_OK:
+            self.configuration.proxies[protocol][1] = dialog.user
+            self.configuration.proxies[protocol][2] = dialog.passwd
+            self.refresh_proxy_components()
+        dialog.destroy()
+
+    def set_save_button(self, button):
+        self.save_button = button
+
+    def rootfs_combo_changed_cb(self, rootfs_combo, all_package_format, check_hbox):
+        combo_item = self.rootfs_combo.get_active_text()
+        modified = False
+        for child in check_hbox.get_children():
+            if isinstance(child, gtk.CheckButton):
+                check_hbox.remove(child)
+                modified = True
+        for format in all_package_format:
+            if format != combo_item:
+                check_button = gtk.CheckButton(format)
+                check_hbox.pack_start(check_button, expand=False, fill=False)
+                modified = True
+        if modified:
+            check_hbox.remove(self.pkgfmt_info)
+            check_hbox.pack_start(self.pkgfmt_info, expand=False, fill=False)
+        check_hbox.show_all()
+
+    def gen_pkgfmt_widget(self, curr_package_format, all_package_format, tooltip_combo="", tooltip_extra=""):
+        pkgfmt_vbox = gtk.VBox(False, 6)
+
+        label = self.gen_label_widget("Root file system package format")
+        pkgfmt_vbox.pack_start(label, expand=False, fill=False)
+
+        rootfs_format = ""
+        if curr_package_format:
+            rootfs_format = curr_package_format.split()[0]
+
+        rootfs_format_widget, rootfs_combo = self.gen_combo_widget(rootfs_format, all_package_format, tooltip_combo)
+        pkgfmt_vbox.pack_start(rootfs_format_widget, expand=False, fill=False)
+
+        label = self.gen_label_widget("Additional package formats")
+        pkgfmt_vbox.pack_start(label, expand=False, fill=False)
+
+        check_hbox = gtk.HBox(False, 12)
+        pkgfmt_vbox.pack_start(check_hbox, expand=False, fill=False)
+        for format in all_package_format:
+            if format != rootfs_format:
+                check_button = gtk.CheckButton(format)
+                is_active = (format in curr_package_format.split())
+                check_button.set_active(is_active)
+                check_hbox.pack_start(check_button, expand=False, fill=False)
+
+        self.pkgfmt_info = HobInfoButton(tooltip_extra, self)
+        check_hbox.pack_start(self.pkgfmt_info, expand=False, fill=False)
+
+        rootfs_combo.connect("changed", self.rootfs_combo_changed_cb, all_package_format, check_hbox)
+
+        pkgfmt_vbox.show_all()
+
+        return pkgfmt_vbox, rootfs_combo, check_hbox
+
+    def __init__(self, title, configuration, all_image_types,
+            all_package_formats, all_distros, all_sdk_machines,
+            max_threads, parent, flags, buttons=None):
+        super(AdvancedSettingsDialog, self).__init__(title, parent, flags, buttons)
+
+        # class members from other objects
+        # bitbake settings from Builder.Configuration
+        self.configuration = configuration
+        self.image_types = all_image_types
+        self.all_package_formats = all_package_formats
+        self.all_distros = all_distros[:]
+        self.all_sdk_machines = all_sdk_machines
+        self.max_threads = max_threads
+
+        # class members for internal use
+        self.distro_combo = None
+        self.dldir_text = None
+        self.sstatedir_text = None
+        self.sstatemirror_text = None
+        self.bb_spinner = None
+        self.pmake_spinner = None
+        self.rootfs_size_spinner = None
+        self.extra_size_spinner = None
+        self.gplv3_checkbox = None
+        self.sdk_checkbox = None
+        self.image_types_checkbuttons = {}
+
+        self.md5 = self.config_md5()
+        self.settings_changed = False
+
+        # create visual elements on the dialog
+        self.save_button = None
+        self.create_visual_elements()
+        self.connect("response", self.response_cb)
+
+    def _get_sorted_value(self, var):
+        return " ".join(sorted(str(var).split())) + "\n"
+
+    def config_md5(self):
+        data = ""
+        data += ("PACKAGE_CLASSES: "      + self.configuration.curr_package_format + '\n')
+        data += ("DISTRO: "               + self._get_sorted_value(self.configuration.curr_distro))
+        data += ("IMAGE_ROOTFS_SIZE: "    + self._get_sorted_value(self.configuration.image_rootfs_size))
+        data += ("IMAGE_EXTRA_SIZE: "     + self._get_sorted_value(self.configuration.image_extra_size))
+        data += ("INCOMPATIBLE_LICENSE: " + self._get_sorted_value(self.configuration.incompat_license))
+        data += ("SDK_MACHINE: "          + self._get_sorted_value(self.configuration.curr_sdk_machine))
+        data += ("TOOLCHAIN_BUILD: "      + self._get_sorted_value(self.configuration.toolchain_build))
+        data += ("IMAGE_FSTYPES: "        + self._get_sorted_value(self.configuration.image_fstypes))
+        return hashlib.md5(data).hexdigest()
+
+    def create_visual_elements(self):
+        self.nb = gtk.Notebook()
+        self.nb.set_show_tabs(True)
+        self.nb.append_page(self.create_image_types_page(), gtk.Label("Image types"))
+        self.nb.append_page(self.create_output_page(), gtk.Label("Output"))
+        self.nb.set_current_page(0)
+        self.vbox.pack_start(self.nb, expand=True, fill=True)
+        self.vbox.pack_end(gtk.HSeparator(), expand=True, fill=True)
+
+        self.show_all()
+
+    def get_num_checked_image_types(self):
+        total = 0
+        for b in self.image_types_checkbuttons.values():
+            if b.get_active():
+              total = total + 1
+        return total
+
+    def set_save_button_state(self):
+        if self.save_button:
+            self.save_button.set_sensitive(self.get_num_checked_image_types() > 0)
+
+    def image_type_checkbutton_clicked_cb(self, button):
+        self.set_save_button_state()
+        if self.get_num_checked_image_types() == 0:
+            # Show an error dialog
+            lbl = "<b>Select an image type</b>"
+            msg = "You need to select at least one image type."
+            dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_WARNING, msg)
+            button = dialog.add_button("OK", gtk.RESPONSE_OK)
+            HobButton.style_button(button)
+            response = dialog.run()
+            dialog.destroy()
+
+    def create_image_types_page(self):
+        main_vbox = gtk.VBox(False, 16)
+        main_vbox.set_border_width(6)
+
+        advanced_vbox = gtk.VBox(False, 6)
+        advanced_vbox.set_border_width(6)
+
+        distro_vbox = gtk.VBox(False, 6)        
+        label = self.gen_label_widget("Distro:")
+        tooltip = "Selects the Yocto Project distribution you want"
+        try:
+            i = self.all_distros.index( "defaultsetup" )
+        except ValueError:
+            i = -1
+        if i != -1:
+            self.all_distros[ i ] = "Default"
+            if self.configuration.curr_distro == "defaultsetup":
+                self.configuration.curr_distro = "Default"
+        distro_widget, self.distro_combo = self.gen_combo_widget(self.configuration.curr_distro, self.all_distros,"<b>Distro</b>" + "*" + tooltip)
+        distro_vbox.pack_start(label, expand=False, fill=False)
+        distro_vbox.pack_start(distro_widget, expand=False, fill=False)
+        main_vbox.pack_start(distro_vbox, expand=False, fill=False)
+
+
+        rows = (len(self.image_types)+1)/3
+        table = gtk.Table(rows + 1, 10, True)
+        advanced_vbox.pack_start(table, expand=False, fill=False)
+
+        tooltip = "Image file system types you want."
+        info = HobInfoButton("<b>Image types</b>" + "*" + tooltip, self)
+        label = self.gen_label_widget("Image types:")
+        align = gtk.Alignment(0, 0.5, 0, 0)
+        table.attach(align, 0, 4, 0, 1)
+        align.add(label)
+        table.attach(info, 4, 5, 0, 1)
+
+        i = 1
+        j = 1
+        for image_type in sorted(self.image_types):
+            self.image_types_checkbuttons[image_type] = gtk.CheckButton(image_type)
+            self.image_types_checkbuttons[image_type].connect("toggled", self.image_type_checkbutton_clicked_cb)
+            article = ""
+            if image_type.startswith(("a", "e", "i", "o", "u")):
+                article = "n"
+            if image_type == "live":
+                self.image_types_checkbuttons[image_type].set_tooltip_text("Build iso and hddimg images")
+            else:
+                self.image_types_checkbuttons[image_type].set_tooltip_text("Build a%s %s image" % (article, image_type))
+            table.attach(self.image_types_checkbuttons[image_type], j - 1, j + 3, i, i + 1)
+            if image_type in self.configuration.image_fstypes.split():
+                self.image_types_checkbuttons[image_type].set_active(True)
+            i += 1
+            if i > rows:
+                i = 1
+                j = j + 4
+
+        main_vbox.pack_start(advanced_vbox, expand=False, fill=False)
+        self.set_save_button_state()
+        
+        return main_vbox
+
+    def create_output_page(self):
+        advanced_vbox = gtk.VBox(False, 6)
+        advanced_vbox.set_border_width(6)
+
+        advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Package format</span>'), expand=False, fill=False)
+        sub_vbox = gtk.VBox(False, 6)
+        advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
+        tooltip_combo = "Selects the package format used to generate rootfs."
+        tooltip_extra = "Selects extra package formats to build"
+        pkgfmt_widget, self.rootfs_combo, self.check_hbox = self.gen_pkgfmt_widget(self.configuration.curr_package_format, self.all_package_formats,"<b>Root file system package format</b>" + "*" + tooltip_combo,"<b>Additional package formats</b>" + "*" + tooltip_extra)
+        sub_vbox.pack_start(pkgfmt_widget, expand=False, fill=False)
+
+        advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Image size</span>'), expand=False, fill=False)
+        sub_vbox = gtk.VBox(False, 6)
+        advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
+        label = self.gen_label_widget("Image basic size (in MB)")
+        tooltip = "Defines the size for the generated image. The OpenEmbedded build system determines the final size for the generated image using an algorithm that takes into account the initial disk space used for the generated image, the Image basic size value, and the Additional free space value.\n\nFor more information, check the <a href=\"http://www.yoctoproject.org/docs/current/poky-ref-manual/poky-ref-manual.html#var-IMAGE_ROOTFS_SIZE\">Yocto Project Reference Manual</a>."
+        rootfs_size_widget, self.rootfs_size_spinner = self.gen_spinner_widget(int(self.configuration.image_rootfs_size*1.0/1024), 0, 65536,"<b>Image basic size</b>" + "*" + tooltip)
+        sub_vbox.pack_start(label, expand=False, fill=False)
+        sub_vbox.pack_start(rootfs_size_widget, expand=False, fill=False)
+
+        sub_vbox = gtk.VBox(False, 6)
+        advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
+        label = self.gen_label_widget("Additional free space (in MB)")
+        tooltip = "Sets extra free disk space to be added to the generated image. Use this variable when you want to ensure that a specific amount of free disk space is available on a device after an image is installed and running."
+        extra_size_widget, self.extra_size_spinner = self.gen_spinner_widget(int(self.configuration.image_extra_size*1.0/1024), 0, 65536,"<b>Additional free space</b>" + "*" + tooltip)
+        sub_vbox.pack_start(label, expand=False, fill=False)
+        sub_vbox.pack_start(extra_size_widget, expand=False, fill=False)
+
+        advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Licensing</span>'), expand=False, fill=False)
+        self.gplv3_checkbox = gtk.CheckButton("Exclude GPLv3 packages")
+        self.gplv3_checkbox.set_tooltip_text("Check this box to prevent GPLv3 packages from being included in your image")
+        if "GPLv3" in self.configuration.incompat_license.split():
+            self.gplv3_checkbox.set_active(True)
+        else:
+            self.gplv3_checkbox.set_active(False)
+        advanced_vbox.pack_start(self.gplv3_checkbox, expand=False, fill=False)
+
+        advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">SDK</span>'), expand=False, fill=False)
+        sub_hbox = gtk.HBox(False, 6)
+        advanced_vbox.pack_start(sub_hbox, expand=False, fill=False)
+        self.sdk_checkbox = gtk.CheckButton("Populate SDK")
+        tooltip = "Check this box to generate an SDK tarball that consists of the cross-toolchain and a sysroot that contains development packages for your image."
+        self.sdk_checkbox.set_tooltip_text(tooltip)
+        self.sdk_checkbox.set_active(self.configuration.toolchain_build)
+        sub_hbox.pack_start(self.sdk_checkbox, expand=False, fill=False)
+
+        tooltip = "Select the host platform for which you want to run the toolchain contained in the SDK tarball."
+        sdk_machine_widget, self.sdk_machine_combo = self.gen_combo_widget(self.configuration.curr_sdk_machine, self.all_sdk_machines,"<b>Populate SDK</b>" + "*" + tooltip)
+        sub_hbox.pack_start(sdk_machine_widget, expand=False, fill=False)
+
+        return advanced_vbox
+
+    def response_cb(self, dialog, response_id):
+        package_format = []
+        package_format.append(self.rootfs_combo.get_active_text())
+        for child in self.check_hbox:
+            if isinstance(child, gtk.CheckButton) and child.get_active():
+                package_format.append(child.get_label())
+        self.configuration.curr_package_format = " ".join(package_format)
+
+        distro = self.distro_combo.get_active_text()
+        if distro == "Default":
+            distro = "defaultsetup"
+        self.configuration.curr_distro = distro
+        self.configuration.image_rootfs_size = self.rootfs_size_spinner.get_value_as_int() * 1024
+        self.configuration.image_extra_size = self.extra_size_spinner.get_value_as_int() * 1024
+
+        self.configuration.image_fstypes = ""
+        for image_type in self.image_types:
+            if self.image_types_checkbuttons[image_type].get_active():
+                self.configuration.image_fstypes += (" " + image_type)
+        self.configuration.image_fstypes.strip()
+
+        if self.gplv3_checkbox.get_active():
+            if "GPLv3" not in self.configuration.incompat_license.split():
+                self.configuration.incompat_license += " GPLv3"
+        else:
+            if "GPLv3" in self.configuration.incompat_license.split():
+                self.configuration.incompat_license = self.configuration.incompat_license.split().remove("GPLv3")
+                self.configuration.incompat_license = " ".join(self.configuration.incompat_license or [])
+        self.configuration.incompat_license = self.configuration.incompat_license.strip()
+
+        self.configuration.toolchain_build = self.sdk_checkbox.get_active()
+        self.configuration.curr_sdk_machine = self.sdk_machine_combo.get_active_text()
+        md5 = self.config_md5()
+        self.settings_changed = (self.md5 != md5)
diff --git a/bitbake/lib/bb/ui/crumbs/hig/crumbsdialog.py b/bitbake/lib/bb/ui/crumbs/hig/crumbsdialog.py
new file mode 100644
index 0000000..c679f9a
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/crumbsdialog.py
@@ -0,0 +1,44 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012   Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+
+"""
+The following are convenience classes for implementing GNOME HIG compliant
+BitBake GUI's
+In summary: spacing = 12px, border-width = 6px
+"""
+
+class CrumbsDialog(gtk.Dialog):
+    """
+    A GNOME HIG compliant dialog widget.
+    Add buttons with gtk.Dialog.add_button or gtk.Dialog.add_buttons
+    """
+    def __init__(self, title="", parent=None, flags=0, buttons=None):
+        super(CrumbsDialog, self).__init__(title, parent, flags, buttons)
+
+        self.set_property("has-separator", False) # note: deprecated in 2.22
+
+        self.set_border_width(6)
+        self.vbox.set_property("spacing", 12)
+        self.action_area.set_property("spacing", 12)
+        self.action_area.set_property("border-width", 6)
diff --git a/bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py b/bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py
new file mode 100644
index 0000000..3b998e4
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/crumbsmessagedialog.py
@@ -0,0 +1,70 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012   Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import glib
+import gtk
+from bb.ui.crumbs.hobwidget import HobIconChecker
+from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
+
+"""
+The following are convenience classes for implementing GNOME HIG compliant
+BitBake GUI's
+In summary: spacing = 12px, border-width = 6px
+"""
+
+class CrumbsMessageDialog(gtk.MessageDialog):
+    """
+    A GNOME HIG compliant dialog widget.
+    Add buttons with gtk.Dialog.add_button or gtk.Dialog.add_buttons
+    """
+    def __init__(self, parent = None, label="", dialog_type = gtk.MESSAGE_QUESTION, msg=""):
+        super(CrumbsMessageDialog, self).__init__(None,
+                                                  gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
+                                                  dialog_type,
+                                                  gtk.BUTTONS_NONE,
+                                                  None)
+
+        self.set_skip_taskbar_hint(False)
+
+        self.set_markup(label)
+
+        if 0 <= len(msg) < 300:
+            self.format_secondary_markup(msg)
+        else:
+            vbox = self.get_message_area()
+            vbox.set_border_width(1)
+            vbox.set_property("spacing", 12)
+            self.textWindow = gtk.ScrolledWindow()
+            self.textWindow.set_shadow_type(gtk.SHADOW_IN)
+            self.textWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+            self.msgView = gtk.TextView()
+            self.msgView.set_editable(False)
+            self.msgView.set_wrap_mode(gtk.WRAP_WORD)
+            self.msgView.set_cursor_visible(False)
+            self.msgView.set_size_request(300, 300)
+            self.buf = gtk.TextBuffer()
+            self.buf.set_text(msg)
+            self.msgView.set_buffer(self.buf)
+            self.textWindow.add(self.msgView)
+            self.msgView.show()
+            vbox.add(self.textWindow)
+            self.textWindow.show()
diff --git a/bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py b/bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py
new file mode 100644
index 0000000..a13fff9
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/deployimagedialog.py
@@ -0,0 +1,219 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012   Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import glob
+import gtk
+import gobject
+import os
+import re
+import shlex
+import subprocess
+import tempfile
+from bb.ui.crumbs.hobwidget import hic, HobButton
+from bb.ui.crumbs.progressbar import HobProgressBar
+import bb.ui.crumbs.utils
+import bb.process
+from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
+from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog
+
+"""
+The following are convenience classes for implementing GNOME HIG compliant
+BitBake GUI's
+In summary: spacing = 12px, border-width = 6px
+"""
+
+class DeployImageDialog (CrumbsDialog):
+
+    __dummy_usb__ = "--select a usb drive--"
+
+    def __init__(self, title, image_path, parent, flags, buttons=None, standalone=False):
+        super(DeployImageDialog, self).__init__(title, parent, flags, buttons)
+
+        self.image_path = image_path
+        self.standalone = standalone
+
+        self.create_visual_elements()
+        self.connect("response", self.response_cb)
+
+    def create_visual_elements(self):
+        self.set_size_request(600, 400)
+        label = gtk.Label()
+        label.set_alignment(0.0, 0.5)
+        markup = "<span font_desc='12'>The image to be written into usb drive:</span>"
+        label.set_markup(markup)
+        self.vbox.pack_start(label, expand=False, fill=False, padding=2)
+
+        table = gtk.Table(2, 10, False)
+        table.set_col_spacings(5)
+        table.set_row_spacings(5)
+        self.vbox.pack_start(table, expand=True, fill=True)
+
+        scroll = gtk.ScrolledWindow()
+        scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+        scroll.set_shadow_type(gtk.SHADOW_IN)
+        tv = gtk.TextView()
+        tv.set_editable(False)
+        tv.set_wrap_mode(gtk.WRAP_WORD)
+        tv.set_cursor_visible(False)
+        self.buf = gtk.TextBuffer()
+        self.buf.set_text(self.image_path)
+        tv.set_buffer(self.buf)
+        scroll.add(tv)
+        table.attach(scroll, 0, 10, 0, 1)
+
+        # There are 2 ways to use DeployImageDialog
+        # One way is that called by HOB when the 'Deploy Image' button is clicked
+        # The other way is that called by a standalone script.
+        # Following block of codes handles the latter way. It adds a 'Select Image' button and
+        # emit a signal when the button is clicked.
+        if self.standalone:
+                gobject.signal_new("select_image_clicked", self, gobject.SIGNAL_RUN_FIRST,
+                                   gobject.TYPE_NONE, ())
+                icon = gtk.Image()
+                pix_buffer = gtk.gdk.pixbuf_new_from_file(hic.ICON_IMAGES_DISPLAY_FILE)
+                icon.set_from_pixbuf(pix_buffer)
+                button = gtk.Button("Select Image")
+                button.set_image(icon)
+                #button.set_size_request(140, 50)
+                table.attach(button, 9, 10, 1, 2, gtk.FILL, 0, 0, 0)
+                button.connect("clicked", self.select_image_button_clicked_cb)
+
+        separator = gtk.HSeparator()
+        self.vbox.pack_start(separator, expand=False, fill=False, padding=10)
+
+        self.usb_desc = gtk.Label()
+        self.usb_desc.set_alignment(0.0, 0.5)
+        markup = "<span font_desc='12'>You haven't chosen any USB drive.</span>"
+        self.usb_desc.set_markup(markup)
+
+        self.usb_combo = gtk.combo_box_new_text()
+        self.usb_combo.connect("changed", self.usb_combo_changed_cb)
+        model = self.usb_combo.get_model()
+        model.clear()
+        self.usb_combo.append_text(self.__dummy_usb__)
+        for usb in self.find_all_usb_devices():
+            self.usb_combo.append_text("/dev/" + usb)
+        self.usb_combo.set_active(0)
+        self.vbox.pack_start(self.usb_combo, expand=False, fill=False)
+        self.vbox.pack_start(self.usb_desc, expand=False, fill=False, padding=2)
+
+        self.progress_bar = HobProgressBar()
+        self.vbox.pack_start(self.progress_bar, expand=False, fill=False)
+        separator = gtk.HSeparator()
+        self.vbox.pack_start(separator, expand=False, fill=True, padding=10)
+
+        self.vbox.show_all()
+        self.progress_bar.hide()
+
+    def set_image_text_buffer(self, image_path):
+        self.buf.set_text(image_path)
+
+    def set_image_path(self, image_path):
+        self.image_path = image_path
+
+    def popen_read(self, cmd):
+        tmpout, errors = bb.process.run("%s" % cmd)
+        return tmpout.strip()
+
+    def find_all_usb_devices(self):
+        usb_devs = [ os.readlink(u)
+            for u in glob.glob('/dev/disk/by-id/usb*')
+            if not re.search(r'part\d+', u) ]
+        return [ '%s' % u[u.rfind('/')+1:] for u in usb_devs ]
+
+    def get_usb_info(self, dev):
+        return "%s %s" % \
+            (self.popen_read('cat /sys/class/block/%s/device/vendor' % dev),
+            self.popen_read('cat /sys/class/block/%s/device/model' % dev))
+
+    def select_image_button_clicked_cb(self, button):
+            self.emit('select_image_clicked')
+
+    def usb_combo_changed_cb(self, usb_combo):
+        combo_item = self.usb_combo.get_active_text()
+        if not combo_item or combo_item == self.__dummy_usb__:
+            markup = "<span font_desc='12'>You haven't chosen any USB drive.</span>"
+            self.usb_desc.set_markup(markup)
+        else:
+            markup = "<span font_desc='12'>" + self.get_usb_info(combo_item.lstrip("/dev/")) + "</span>"
+            self.usb_desc.set_markup(markup)
+
+    def response_cb(self, dialog, response_id):
+        if response_id == gtk.RESPONSE_YES:
+            lbl = ''
+            msg = ''
+            combo_item = self.usb_combo.get_active_text()
+            if combo_item and combo_item != self.__dummy_usb__ and self.image_path:
+                cmdline = bb.ui.crumbs.utils.which_terminal()
+                if cmdline:
+                    tmpfile = tempfile.NamedTemporaryFile()
+                    cmdline += "\"sudo dd if=" + self.image_path + \
+                                " of=" + combo_item + " && sync; echo $? > " + tmpfile.name + "\""
+                    subprocess.call(shlex.split(cmdline))
+
+                    if int(tmpfile.readline().strip()) == 0:
+                        lbl = "<b>Deploy image successfully.</b>"
+                    else:
+                        lbl = "<b>Failed to deploy image.</b>"
+                        msg = "Please check image <b>%s</b> exists and USB device <b>%s</b> is writable." % (self.image_path, combo_item)
+                    tmpfile.close()
+            else:
+                if not self.image_path:
+                    lbl = "<b>No selection made.</b>"
+                    msg = "You have not selected an image to deploy."
+                else:
+                    lbl = "<b>No selection made.</b>"
+                    msg = "You have not selected a USB device."
+            if len(lbl):
+                crumbs_dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_INFO, msg)
+                button = crumbs_dialog.add_button("Close", gtk.RESPONSE_OK)
+                HobButton.style_button(button)
+                crumbs_dialog.run()
+                crumbs_dialog.destroy()
+
+    def update_progress_bar(self, title, fraction, status=None):
+        self.progress_bar.update(fraction)
+        self.progress_bar.set_title(title)
+        self.progress_bar.set_rcstyle(status)
+
+    def write_file(self, ifile, ofile):
+        self.progress_bar.reset()
+        self.progress_bar.show()
+
+        f_from = os.open(ifile, os.O_RDONLY)
+        f_to = os.open(ofile, os.O_WRONLY)
+
+        total_size = os.stat(ifile).st_size
+        written_size = 0
+
+        while True:
+            buf = os.read(f_from, 1024*1024)
+            if not buf:
+                break
+            os.write(f_to, buf)
+            written_size += 1024*1024
+            self.update_progress_bar("Writing to usb:", written_size * 1.0/total_size)
+
+        self.update_progress_bar("Writing completed:", 1.0)
+        os.close(f_from)
+        os.close(f_to)
+        self.progress_bar.hide()
diff --git a/bitbake/lib/bb/ui/crumbs/hig/imageselectiondialog.py b/bitbake/lib/bb/ui/crumbs/hig/imageselectiondialog.py
new file mode 100644
index 0000000..21216ad
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/imageselectiondialog.py
@@ -0,0 +1,172 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012   Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import gobject
+import os
+from bb.ui.crumbs.hobwidget import HobViewTable, HobInfoButton, HobButton, HobAltButton
+from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
+from bb.ui.crumbs.hig.layerselectiondialog import LayerSelectionDialog
+
+"""
+The following are convenience classes for implementing GNOME HIG compliant
+BitBake GUI's
+In summary: spacing = 12px, border-width = 6px
+"""
+
+class ImageSelectionDialog (CrumbsDialog):
+
+    __columns__ = [{
+            'col_name' : 'Image name',
+            'col_id'   : 0,
+            'col_style': 'text',
+            'col_min'  : 400,
+            'col_max'  : 400
+        }, {
+            'col_name' : 'Select',
+            'col_id'   : 1,
+            'col_style': 'radio toggle',
+            'col_min'  : 160,
+            'col_max'  : 160
+    }]
+
+
+    def __init__(self, image_folder, image_types, title, parent, flags, buttons=None, image_extension = {}):
+        super(ImageSelectionDialog, self).__init__(title, parent, flags, buttons)
+        self.connect("response", self.response_cb)
+
+        self.image_folder = image_folder
+        self.image_types  = image_types
+        self.image_list = []
+        self.image_names = []
+        self.image_extension = image_extension
+
+        # create visual elements on the dialog
+        self.create_visual_elements()
+
+        self.image_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
+        self.fill_image_store()
+
+    def create_visual_elements(self):
+        hbox = gtk.HBox(False, 6)
+
+        self.vbox.pack_start(hbox, expand=False, fill=False)
+
+        entry = gtk.Entry()
+        entry.set_text(self.image_folder)
+        table = gtk.Table(1, 10, True)
+        table.set_size_request(560, -1)
+        hbox.pack_start(table, expand=False, fill=False)
+        table.attach(entry, 0, 9, 0, 1)
+        image = gtk.Image()
+        image.set_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON)
+        open_button = gtk.Button()
+        open_button.set_image(image)
+        open_button.connect("clicked", self.select_path_cb, self, entry)
+        table.attach(open_button, 9, 10, 0, 1)
+
+        self.image_table = HobViewTable(self.__columns__, "Images")
+        self.image_table.set_size_request(-1, 300)
+        self.image_table.connect("toggled", self.toggled_cb)
+        self.image_table.connect_group_selection(self.table_selected_cb)
+        self.image_table.connect("row-activated", self.row_actived_cb)
+        self.vbox.pack_start(self.image_table, expand=True, fill=True)
+
+        self.show_all()
+
+    def change_image_cb(self, model, path, columnid):
+        if not model:
+            return
+        iter = model.get_iter_first()
+        while iter:
+            rowpath = model.get_path(iter)
+            model[rowpath][columnid] = False
+            iter = model.iter_next(iter)
+
+        model[path][columnid] = True
+
+    def toggled_cb(self, table, cell, path, columnid, tree):
+        model = tree.get_model()
+        self.change_image_cb(model, path, columnid)
+
+    def table_selected_cb(self, selection):
+        model, paths = selection.get_selected_rows()
+        if paths:
+            self.change_image_cb(model, paths[0], 1)
+
+    def row_actived_cb(self, tab, model, path):
+        self.change_image_cb(model, path, 1)
+        self.emit('response', gtk.RESPONSE_YES)
+
+    def select_path_cb(self, action, parent, entry):
+        dialog = gtk.FileChooserDialog("", parent,
+                                       gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
+        text = entry.get_text()
+        dialog.set_current_folder(text if len(text) > 0 else os.getcwd())
+        button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
+        HobAltButton.style_button(button)
+        button = dialog.add_button("Open", gtk.RESPONSE_YES)
+        HobButton.style_button(button)
+        response = dialog.run()
+        if response == gtk.RESPONSE_YES:
+            path = dialog.get_filename()
+            entry.set_text(path)
+            self.image_folder = path
+            self.fill_image_store()
+
+        dialog.destroy()
+
+    def fill_image_store(self):
+        self.image_list = []
+        self.image_store.clear()
+        imageset = set()
+        for root, dirs, files in os.walk(self.image_folder):
+            # ignore the sub directories
+            dirs[:] = []
+            for f in files:
+                for image_type in self.image_types:
+                    if image_type in self.image_extension:
+                        real_types = self.image_extension[image_type]
+                    else:
+                        real_types = [image_type]
+                    for real_image_type in real_types:
+                        if f.endswith('.' + real_image_type):
+                            imageset.add(f.rsplit('.' + real_image_type)[0].rsplit('.rootfs')[0])
+                            self.image_list.append(f)
+
+        for image in imageset:
+            self.image_store.set(self.image_store.append(), 0, image, 1, False)
+
+        self.image_table.set_model(self.image_store)
+
+    def response_cb(self, dialog, response_id):
+        self.image_names = []
+        if response_id == gtk.RESPONSE_YES:
+            iter = self.image_store.get_iter_first()
+            while iter:
+                path = self.image_store.get_path(iter)
+                if self.image_store[path][1]:
+                    for f in self.image_list:
+                        if f.startswith(self.image_store[path][0] + '.'):
+                            self.image_names.append(f)
+                    break
+                iter = self.image_store.iter_next(iter)
diff --git a/bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py b/bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py
new file mode 100644
index 0000000..52d57b6
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/layerselectiondialog.py
@@ -0,0 +1,298 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012   Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import gobject
+import os
+import tempfile
+from bb.ui.crumbs.hobwidget import hic, HobButton, HobAltButton
+from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
+from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog
+
+"""
+The following are convenience classes for implementing GNOME HIG compliant
+BitBake GUI's
+In summary: spacing = 12px, border-width = 6px
+"""
+
+class CellRendererPixbufActivatable(gtk.CellRendererPixbuf):
+    """
+    A custom CellRenderer implementation which is activatable
+    so that we can handle user clicks
+    """
+    __gsignals__    = { 'clicked' : (gobject.SIGNAL_RUN_LAST,
+                                     gobject.TYPE_NONE,
+                                     (gobject.TYPE_STRING,)), }
+
+    def __init__(self):
+        gtk.CellRendererPixbuf.__init__(self)
+        self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
+        self.set_property('follow-state', True)
+
+    """
+    Respond to a user click on a cell
+    """
+    def do_activate(self, even, widget, path, background_area, cell_area, flags):
+        self.emit('clicked', path)
+
+#
+# LayerSelectionDialog
+#
+class LayerSelectionDialog (CrumbsDialog):
+
+    TARGETS = [
+        ("MY_TREE_MODEL_ROW", gtk.TARGET_SAME_WIDGET, 0),
+        ("text/plain", 0, 1),
+        ("TEXT", 0, 2),
+        ("STRING", 0, 3),
+        ]
+
+    def gen_label_widget(self, content):
+        label = gtk.Label()
+        label.set_alignment(0, 0)
+        label.set_markup(content)
+        label.show()
+        return label
+
+    def layer_widget_toggled_cb(self, cell, path, layer_store):
+        name = layer_store[path][0]
+        toggle = not layer_store[path][1]
+        layer_store[path][1] = toggle
+
+    def layer_widget_add_clicked_cb(self, action, layer_store, parent):
+        dialog = gtk.FileChooserDialog("Add new layer", parent,
+                                       gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
+        button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
+        HobAltButton.style_button(button)
+        button = dialog.add_button("Open", gtk.RESPONSE_YES)
+        HobButton.style_button(button)
+        label = gtk.Label("Select the layer you wish to add")
+        label.show()
+        dialog.set_extra_widget(label)
+        response = dialog.run()
+        path = dialog.get_filename()
+        dialog.destroy()
+
+        lbl = "<b>Error</b>"
+        msg = "Unable to load layer <i>%s</i> because " % path
+        if response == gtk.RESPONSE_YES:
+            import os
+            import os.path
+            layers = []
+            it = layer_store.get_iter_first()
+            while it:
+                layers.append(layer_store.get_value(it, 0))
+                it = layer_store.iter_next(it)
+
+            if not path:
+                msg += "it is an invalid path."
+            elif not os.path.exists(path+"/conf/layer.conf"):
+                msg += "there is no layer.conf inside the directory."
+            elif path in layers:
+                msg += "it is already in loaded layers."
+            else:
+                layer_store.append([path])
+                return
+            dialog = CrumbsMessageDialog(parent, lbl, gtk.MESSAGE_ERROR, msg)
+            dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK)
+            response = dialog.run()
+            dialog.destroy()
+
+    def layer_widget_del_clicked_cb(self, action, tree_selection, layer_store):
+        model, iter = tree_selection.get_selected()
+        if iter:
+            layer_store.remove(iter)
+
+
+    def gen_layer_widget(self, layers, layers_avail, window, tooltip=""):
+        hbox = gtk.HBox(False, 6)
+
+        layer_tv = gtk.TreeView()
+        layer_tv.set_rules_hint(True)
+        layer_tv.set_headers_visible(False)
+        tree_selection = layer_tv.get_selection()
+        tree_selection.set_mode(gtk.SELECTION_SINGLE)
+
+        # Allow enable drag and drop of rows including row move
+        dnd_internal_target = ''
+        dnd_targets = [(dnd_internal_target, gtk.TARGET_SAME_WIDGET, 0)]
+        layer_tv.enable_model_drag_source( gtk.gdk.BUTTON1_MASK,
+            dnd_targets,
+            gtk.gdk.ACTION_MOVE)
+        layer_tv.enable_model_drag_dest(dnd_targets,
+            gtk.gdk.ACTION_MOVE)
+        layer_tv.connect("drag_data_get", self.drag_data_get_cb)
+        layer_tv.connect("drag_data_received", self.drag_data_received_cb)
+
+        col0= gtk.TreeViewColumn('Path')
+        cell0 = gtk.CellRendererText()
+        cell0.set_padding(5,2)
+        col0.pack_start(cell0, True)
+        col0.set_cell_data_func(cell0, self.draw_layer_path_cb)
+        layer_tv.append_column(col0)
+
+        scroll = gtk.ScrolledWindow()
+        scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+        scroll.set_shadow_type(gtk.SHADOW_IN)
+        scroll.add(layer_tv)
+
+        table_layer = gtk.Table(2, 10, False)
+        hbox.pack_start(table_layer, expand=True, fill=True)
+
+        table_layer.attach(scroll, 0, 10, 0, 1)
+
+        layer_store = gtk.ListStore(gobject.TYPE_STRING)
+        for layer in layers:
+            layer_store.append([layer])
+
+        col1 = gtk.TreeViewColumn('Enabled')
+        layer_tv.append_column(col1)
+
+        cell1 = CellRendererPixbufActivatable()
+        cell1.set_fixed_size(-1,35)
+        cell1.connect("clicked", self.del_cell_clicked_cb, layer_store)
+        col1.pack_start(cell1, True)
+        col1.set_cell_data_func(cell1, self.draw_delete_button_cb, layer_tv)
+
+        add_button = gtk.Button()
+        add_button.set_relief(gtk.RELIEF_NONE)
+        box = gtk.HBox(False, 6)
+        box.show()
+        add_button.add(box)
+        add_button.connect("enter-notify-event", self.add_hover_cb)
+        add_button.connect("leave-notify-event", self.add_leave_cb)
+        self.im = gtk.Image()
+        self.im.set_from_file(hic.ICON_INDI_ADD_FILE)
+        self.im.show()
+        box.pack_start(self.im, expand=False, fill=False, padding=6)
+        lbl = gtk.Label("Add layer")
+        lbl.set_alignment(0.0, 0.5)
+        lbl.show()
+        box.pack_start(lbl, expand=True, fill=True, padding=6)
+        add_button.connect("clicked", self.layer_widget_add_clicked_cb, layer_store, window)
+        table_layer.attach(add_button, 0, 10, 1, 2, gtk.EXPAND | gtk.FILL, 0, 0, 6)
+        layer_tv.set_model(layer_store)
+
+        hbox.show_all()
+
+        return hbox, layer_store
+
+    def drag_data_get_cb(self, treeview, context, selection, target_id, etime):
+        treeselection = treeview.get_selection()
+        model, iter = treeselection.get_selected()
+        data = model.get_value(iter, 0)
+        selection.set(selection.target, 8, data)
+
+    def drag_data_received_cb(self, treeview, context, x, y, selection, info, etime):
+        model = treeview.get_model()
+        data = selection.data
+        drop_info = treeview.get_dest_row_at_pos(x, y)
+        if drop_info:
+            path, position = drop_info
+            iter = model.get_iter(path)
+            if (position == gtk.TREE_VIEW_DROP_BEFORE or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
+                model.insert_before(iter, [data])
+            else:
+                model.insert_after(iter, [data])
+        else:
+            model.append([data])
+        if context.action == gtk.gdk.ACTION_MOVE:
+            context.finish(True, True, etime)
+        return
+
+    def add_hover_cb(self, button, event):
+        self.im.set_from_file(hic.ICON_INDI_ADD_HOVER_FILE)
+
+    def add_leave_cb(self, button, event):
+        self.im.set_from_file(hic.ICON_INDI_ADD_FILE)
+
+    def __init__(self, title, layers, layers_non_removable, all_layers, parent, flags, buttons=None):
+        super(LayerSelectionDialog, self).__init__(title, parent, flags, buttons)
+
+        # class members from other objects
+        self.layers = layers
+        self.layers_non_removable = layers_non_removable
+        self.all_layers = all_layers
+        self.layers_changed = False
+
+        # icon for remove button in TreeView
+        im = gtk.Image()
+        im.set_from_file(hic.ICON_INDI_REMOVE_FILE)
+        self.rem_icon = im.get_pixbuf()
+
+        # class members for internal use
+        self.layer_store = None
+
+        # create visual elements on the dialog
+        self.create_visual_elements()
+        self.connect("response", self.response_cb)
+
+    def create_visual_elements(self):
+        layer_widget, self.layer_store = self.gen_layer_widget(self.layers, self.all_layers, self, None)
+        layer_widget.set_size_request(450, 250)
+        self.vbox.pack_start(layer_widget, expand=True, fill=True)
+        self.show_all()
+
+    def response_cb(self, dialog, response_id):
+        model = self.layer_store
+        it = model.get_iter_first()
+        layers = []
+        while it:
+            layers.append(model.get_value(it, 0))
+            it = model.iter_next(it)
+
+        self.layers_changed = (self.layers != layers)
+        self.layers = layers
+
+    """
+    A custom cell_data_func to draw a delete 'button' in the TreeView for layers
+    other than the meta layer. The deletion of which is prevented so that the
+    user can't shoot themselves in the foot too badly.
+    """
+    def draw_delete_button_cb(self, col, cell, model, it, tv):
+        path =  model.get_value(it, 0)
+        if path in self.layers_non_removable:
+            cell.set_sensitive(False)
+            cell.set_property('pixbuf', None)
+            cell.set_property('mode', gtk.CELL_RENDERER_MODE_INERT)
+        else:
+            cell.set_property('pixbuf', self.rem_icon)
+            cell.set_sensitive(True)
+            cell.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
+
+        return True
+
+    """
+    A custom cell_data_func to write an extra message into the layer path cell
+    for the meta layer. We should inform the user that they can't remove it for
+    their own safety.
+    """
+    def draw_layer_path_cb(self, col, cell, model, it):
+        path = model.get_value(it, 0)
+        if path in self.layers_non_removable:
+            cell.set_property('markup', "<b>It cannot be removed</b>\n%s" % path)
+        else:
+            cell.set_property('text', path)
+
+    def del_cell_clicked_cb(self, cell, path, model):
+        it = model.get_iter_from_string(path)
+        model.remove(it)
diff --git a/bitbake/lib/bb/ui/crumbs/hig/parsingwarningsdialog.py b/bitbake/lib/bb/ui/crumbs/hig/parsingwarningsdialog.py
new file mode 100644
index 0000000..33bac39
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/parsingwarningsdialog.py
@@ -0,0 +1,163 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012   Intel Corporation
+#
+# Authored by Cristiana Voicu <cristiana.voicu@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import gobject
+from bb.ui.crumbs.hobwidget import HobAltButton
+from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
+
+"""
+The following are convenience classes for implementing GNOME HIG compliant
+BitBake GUI's
+In summary: spacing = 12px, border-width = 6px
+"""
+
+#
+# ParsingWarningsDialog
+#
+class ParsingWarningsDialog (CrumbsDialog):
+
+    def __init__(self, title, warnings, parent, flags, buttons=None):
+        super(ParsingWarningsDialog, self).__init__(title, parent, flags, buttons)
+
+        self.warnings = warnings
+        self.warning_on = 0
+        self.warn_nb = len(warnings)
+
+        # create visual elements on the dialog
+        self.create_visual_elements()
+
+    def cancel_button_cb(self, button):
+        self.destroy()
+
+    def previous_button_cb(self, button):
+        self.warning_on = self.warning_on - 1
+        self.refresh_components()
+
+    def next_button_cb(self, button):
+        self.warning_on = self.warning_on + 1
+        self.refresh_components()
+
+    def refresh_components(self):
+        lbl = self.warnings[self.warning_on]
+        #when the warning text has more than 400 chars, it uses a scroll bar
+        if 0<= len(lbl) < 400:
+            self.warning_label.set_size_request(320, 230)
+            self.warning_label.set_use_markup(True)
+            self.warning_label.set_line_wrap(True)
+            self.warning_label.set_markup(lbl)
+            self.warning_label.set_property("yalign", 0.00)
+        else:
+            self.textWindow.set_shadow_type(gtk.SHADOW_IN)
+            self.textWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+            self.msgView = gtk.TextView()
+            self.msgView.set_editable(False)
+            self.msgView.set_wrap_mode(gtk.WRAP_WORD)
+            self.msgView.set_cursor_visible(False)
+            self.msgView.set_size_request(320, 230)
+            self.buf = gtk.TextBuffer()
+            self.buf.set_text(lbl)
+            self.msgView.set_buffer(self.buf)
+            self.textWindow.add(self.msgView)
+            self.msgView.show()
+
+        if self.warning_on==0:
+            self.previous_button.set_sensitive(False)
+        else:
+            self.previous_button.set_sensitive(True)
+
+        if self.warning_on==self.warn_nb-1:
+            self.next_button.set_sensitive(False)
+        else:
+            self.next_button.set_sensitive(True)
+
+        if self.warn_nb>1:
+            self.heading = "Warning " + str(self.warning_on + 1) + " of " + str(self.warn_nb)
+            self.heading_label.set_markup('<span weight="bold">%s</span>' % self.heading)
+        else:
+            self.heading = "Warning"
+            self.heading_label.set_markup('<span weight="bold">%s</span>' % self.heading)
+
+        self.show_all()
+
+        if 0<= len(lbl) < 400:
+            self.textWindow.hide()
+        else:
+            self.warning_label.hide()
+
+    def create_visual_elements(self):
+        self.set_size_request(350, 350)
+        self.heading_label = gtk.Label()
+        self.heading_label.set_alignment(0, 0)
+        self.warning_label = gtk.Label()
+        self.warning_label.set_selectable(True)
+        self.warning_label.set_alignment(0, 0)
+        self.textWindow = gtk.ScrolledWindow()
+
+        table = gtk.Table(1, 10, False)
+
+        cancel_button = gtk.Button()
+        cancel_button.set_label("Close")
+        cancel_button.connect("clicked", self.cancel_button_cb)
+        cancel_button.set_size_request(110, 30)
+
+        self.previous_button = gtk.Button()
+        image1 = gtk.image_new_from_stock(gtk.STOCK_GO_BACK, gtk.ICON_SIZE_BUTTON)
+        image1.show()
+        box = gtk.HBox(False, 6)
+        box.show()
+        self.previous_button.add(box)
+        lbl = gtk.Label("Previous")
+        lbl.show()
+        box.pack_start(image1, expand=False, fill=False, padding=3)
+        box.pack_start(lbl, expand=True, fill=True, padding=3)
+        self.previous_button.connect("clicked", self.previous_button_cb)
+        self.previous_button.set_size_request(110, 30)
+
+        self.next_button = gtk.Button()
+        image2 = gtk.image_new_from_stock(gtk.STOCK_GO_FORWARD, gtk.ICON_SIZE_BUTTON)
+        image2.show()
+        box = gtk.HBox(False, 6)
+        box.show()
+        self.next_button.add(box)
+        lbl = gtk.Label("Next")
+        lbl.show()
+        box.pack_start(lbl, expand=True, fill=True, padding=3)
+        box.pack_start(image2, expand=False, fill=False, padding=3)
+        self.next_button.connect("clicked", self.next_button_cb)
+        self.next_button.set_size_request(110, 30)
+
+        #when there more than one warning, we need "previous" and "next" button
+        if self.warn_nb>1:
+            self.vbox.pack_start(self.heading_label, expand=False, fill=False)
+            self.vbox.pack_start(self.warning_label, expand=False, fill=False)
+            self.vbox.pack_start(self.textWindow, expand=False, fill=False)
+            table.attach(cancel_button, 6, 7, 0, 1, xoptions=gtk.SHRINK)
+            table.attach(self.previous_button, 7, 8, 0, 1, xoptions=gtk.SHRINK)
+            table.attach(self.next_button, 8, 9, 0, 1, xoptions=gtk.SHRINK)
+            self.vbox.pack_end(table, expand=False, fill=False)
+        else:
+            self.vbox.pack_start(self.heading_label, expand=False, fill=False)
+            self.vbox.pack_start(self.warning_label, expand=False, fill=False)
+            self.vbox.pack_start(self.textWindow, expand=False, fill=False)
+            cancel_button = self.add_button("Close", gtk.RESPONSE_CANCEL)
+            HobAltButton.style_button(cancel_button)
+
+        self.refresh_components()
diff --git a/bitbake/lib/bb/ui/crumbs/hig/propertydialog.py b/bitbake/lib/bb/ui/crumbs/hig/propertydialog.py
new file mode 100644
index 0000000..09b9ce6
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/propertydialog.py
@@ -0,0 +1,437 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2013   Intel Corporation
+#
+# Authored by Andrei Dinu <andrei.adrianx.dinu@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import string
+import gtk
+import gobject
+import os
+import tempfile
+import glib
+from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
+from bb.ui.crumbs.hig.settingsuihelper import SettingsUIHelper
+from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog
+from bb.ui.crumbs.hig.layerselectiondialog import LayerSelectionDialog
+
+"""
+The following are convenience classes for implementing GNOME HIG compliant
+BitBake GUI's
+In summary: spacing = 12px, border-width = 6px
+"""
+
+class PropertyDialog(CrumbsDialog):
+	
+	def __init__(self, title, parent, information, flags, buttons=None):
+		
+		super(PropertyDialog, self).__init__(title, parent, flags, buttons)
+                
+                self.properties = information
+
+                if len(self.properties) == 10:
+		        self.create_recipe_visual_elements()
+                elif len(self.properties) == 5:
+                        self.create_package_visual_elements()
+                else:
+                        self.create_information_visual_elements()
+
+
+        def create_information_visual_elements(self):
+
+                HOB_ICON_BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ("icons/"))
+                ICON_PACKAGES_DISPLAY_FILE    = os.path.join(HOB_ICON_BASE_DIR, ('info/info_display.png'))
+
+                self.set_resizable(False)
+
+                self.table = gtk.Table(1,1,False)
+                self.table.set_row_spacings(0)
+                self.table.set_col_spacings(0)
+
+                self.image = gtk.Image()
+                self.image.set_from_file(ICON_PACKAGES_DISPLAY_FILE)
+                self.image.set_property("xalign",0)
+                #self.vbox.add(self.image)
+
+                image_info = self.properties.split("*")[0]
+                info = self.properties.split("*")[1]
+                
+                vbox = gtk.VBox(True, spacing=30)        
+                
+                self.label_short = gtk.Label()
+                self.label_short.set_line_wrap(False)
+                self.label_short.set_markup(image_info)
+                self.label_short.set_property("xalign", 0)
+
+                self.info_label = gtk.Label()
+                self.info_label.set_line_wrap(True)
+                self.info_label.set_markup(info)
+                self.info_label.set_property("yalign", 0.5)
+
+                self.table.attach(self.image, 0,1,0,1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL,xpadding=5,ypadding=5)
+                self.table.attach(self.label_short, 0,1,0,1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL,xpadding=40,ypadding=5)
+                self.table.attach(self.info_label, 0,1,1,2, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL,xpadding=40,ypadding=10)
+        
+                self.vbox.add(self.table)
+                self.connect('delete-event', lambda w, e: self.destroy() or True) 
+
+        def treeViewTooltip( self, widget, e, tooltips, cell, emptyText="" ):
+                 try:
+                           (path,col,x,y) = widget.get_path_at_pos( int(e.x), int(e.y) )
+                           it = widget.get_model().get_iter(path)
+                           value = widget.get_model().get_value(it,cell)
+                           if value in self.tooltip_items:
+                                tooltips.set_tip(widget, self.tooltip_items[value])
+                                tooltips.enable()
+                           else:
+                                tooltips.set_tip(widget, emptyText)
+                 except:
+                           tooltips.set_tip(widget, emptyText)
+
+                
+        def create_package_visual_elements(self):
+
+                import json
+
+                name = self.properties['name']
+                binb = self.properties['binb']
+                size = self.properties['size']
+                recipe = self.properties['recipe']
+                file_list = json.loads(self.properties['files_list'])
+
+                files_temp = ''
+                paths_temp = ''
+                files_binb = []
+                paths_binb = []
+
+                self.tooltip_items = {}
+
+                self.set_resizable(False)
+                
+                #cleaning out the recipe variable
+                recipe = recipe.split("+")[0]
+
+                vbox = gtk.VBox(True,spacing = 0)
+
+                ###################################### NAME ROW + COL #################################
+
+                self.label_short = gtk.Label()
+                self.label_short.set_size_request(300,-1)
+                self.label_short.set_selectable(True)
+                self.label_short.set_line_wrap(True)
+                self.label_short.set_markup("<span weight=\"bold\">Name: </span>" + name)
+                self.label_short.set_property("xalign", 0)
+                                
+                self.vbox.add(self.label_short)
+
+                ###################################### SIZE ROW + COL ######################################
+
+                self.label_short = gtk.Label()
+                self.label_short.set_size_request(300,-1)
+                self.label_short.set_selectable(True)
+                self.label_short.set_line_wrap(True)
+                self.label_short.set_markup("<span weight=\"bold\">Size: </span>" + size)
+                self.label_short.set_property("xalign", 0)
+                
+                self.vbox.add(self.label_short)
+
+                ##################################### RECIPE ROW + COL #########################################
+
+                self.label_short = gtk.Label()
+                self.label_short.set_size_request(300,-1)
+                self.label_short.set_selectable(True)
+                self.label_short.set_line_wrap(True)
+                self.label_short.set_markup("<span weight=\"bold\">Recipe: </span>" + recipe)
+                self.label_short.set_property("xalign", 0)
+
+                self.vbox.add(self.label_short)
+
+                ##################################### BINB ROW + COL #######################################
+                
+                if binb != '':
+                        self.label_short = gtk.Label()
+                        self.label_short.set_selectable(True)
+                        self.label_short.set_line_wrap(True)
+                        self.label_short.set_markup("<span weight=\"bold\">Brought in by: </span>")
+                        self.label_short.set_property("xalign", 0)                
+
+                        self.label_info = gtk.Label()
+                        self.label_info.set_size_request(300,-1)
+                        self.label_info.set_selectable(True)
+                        self.label_info.set_line_wrap(True)
+                        self.label_info.set_markup(binb)
+                        self.label_info.set_property("xalign", 0)               
+                                                        
+                        self.vbox.add(self.label_short)
+                        self.vbox.add(self.label_info)
+
+                #################################### FILES BROUGHT BY PACKAGES ###################################
+
+                if file_list:
+                
+                        self.textWindow = gtk.ScrolledWindow()
+                        self.textWindow.set_shadow_type(gtk.SHADOW_IN)
+                        self.textWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+                        self.textWindow.set_size_request(100, 170)
+
+                        packagefiles_store = gtk.ListStore(str)
+
+                        self.packagefiles_tv = gtk.TreeView()
+                        self.packagefiles_tv.set_rules_hint(True)
+                        self.packagefiles_tv.set_headers_visible(True)
+                        self.textWindow.add(self.packagefiles_tv)
+
+                        self.cell1 = gtk.CellRendererText()
+                        col1 = gtk.TreeViewColumn('Package files', self.cell1)
+                        col1.set_cell_data_func(self.cell1, self.regex_field)
+                        self.packagefiles_tv.append_column(col1)
+
+                        items = file_list.keys()
+                        items.sort()
+                        for item in items:
+                                fullpath = item
+                                while len(item) > 35:
+                                        item = item[:len(item)/2] + "" + item[len(item)/2+1:]
+                                if len(item) == 35:
+                                        item = item[:len(item)/2] + "..." + item[len(item)/2+3:]
+                                        self.tooltip_items[item] = fullpath
+
+                                packagefiles_store.append([str(item)])
+
+                        self.packagefiles_tv.set_model(packagefiles_store)
+
+                        tips = gtk.Tooltips()
+                        tips.set_tip(self.packagefiles_tv, "")
+                        self.packagefiles_tv.connect("motion-notify-event", self.treeViewTooltip, tips, 0)
+                        self.packagefiles_tv.set_events(gtk.gdk.POINTER_MOTION_MASK)
+                        
+                        self.vbox.add(self.textWindow)                                      
+
+                self.vbox.show_all()
+
+
+        def regex_field(self, column, cell, model, iter):
+                cell.set_property('text', model.get_value(iter, 0))
+                return
+
+
+	def create_recipe_visual_elements(self):
+
+                summary = self.properties['summary']
+		name = self.properties['name']
+		version = self.properties['version']
+		revision = self.properties['revision']
+		binb = self.properties['binb']
+		group = self.properties['group']
+		license = self.properties['license']
+		homepage = self.properties['homepage']
+		bugtracker = self.properties['bugtracker']
+		description = self.properties['description']
+
+                self.set_resizable(False)
+		
+                #cleaning out the version variable and also the summary
+                version = version.split(":")[1]
+                if len(version) > 30:
+                        version = version.split("+")[0]
+                else:
+                        version = version.split("-")[0]
+                license = license.replace("&" , "and")
+                if (homepage == ''):
+                        homepage = 'unknown'
+                if (bugtracker == ''):
+                        bugtracker = 'unknown'
+                summary = summary.split("+")[0]
+                
+                #calculating the rows needed for the table
+                binb_items_count = len(binb.split(','))
+                binb_items = binb.split(',')
+                               		
+		vbox = gtk.VBox(False,spacing = 0)
+
+                ######################################## SUMMARY LABEL #########################################                	                
+                
+                if summary != '':                
+                        self.label_short = gtk.Label()
+                        self.label_short.set_width_chars(37)
+                        self.label_short.set_selectable(True)                
+                        self.label_short.set_line_wrap(True)
+                        self.label_short.set_markup("<b>" + summary + "</b>")
+                        self.label_short.set_property("xalign", 0)
+                        
+                        self.vbox.add(self.label_short)
+                
+                ########################################## NAME ROW + COL #######################################
+                                
+                self.label_short = gtk.Label()
+                self.label_short.set_selectable(True)
+                self.label_short.set_line_wrap(True)
+                self.label_short.set_markup("<span weight=\"bold\">Name: </span>" + name)
+                self.label_short.set_property("xalign", 0)
+                                
+                self.vbox.add(self.label_short)
+
+                ####################################### VERSION ROW + COL ####################################
+                
+                self.label_short = gtk.Label()
+                self.label_short.set_selectable(True)
+                self.label_short.set_line_wrap(True)
+                self.label_short.set_markup("<span weight=\"bold\">Version: </span>" + version)
+                self.label_short.set_property("xalign", 0)
+                
+                self.vbox.add(self.label_short)
+
+                ##################################### REVISION ROW + COL #####################################
+
+                self.label_short = gtk.Label()
+                self.label_short.set_line_wrap(True)
+                self.label_short.set_selectable(True)
+                self.label_short.set_markup("<span weight=\"bold\">Revision: </span>" + revision)
+                self.label_short.set_property("xalign", 0)                
+                
+                self.vbox.add(self.label_short)
+
+                ################################## GROUP ROW + COL ############################################
+               
+                self.label_short = gtk.Label()
+                self.label_short.set_selectable(True)              
+                self.label_short.set_line_wrap(True)                                
+                self.label_short.set_markup("<span weight=\"bold\">Group: </span>" + group)
+                self.label_short.set_property("xalign", 0)
+                                
+                self.vbox.add(self.label_short)
+
+                ################################# HOMEPAGE ROW + COL ############################################
+                
+                if homepage != 'unknown':
+                        self.label_info = gtk.Label()
+                        self.label_info.set_selectable(True)
+                        self.label_info.set_line_wrap(True)
+                        if len(homepage) > 35:
+                                self.label_info.set_markup("<a href=\"" + homepage + "\">" + homepage[0:35] + "..." + "</a>")
+                        else:
+                                self.label_info.set_markup("<a href=\"" + homepage + "\">" + homepage[0:60] + "</a>")
+
+                        self.label_info.set_property("xalign", 0)
+                       
+                        self.label_short = gtk.Label()
+                        self.label_short.set_selectable(True)                     
+                        self.label_short.set_line_wrap(True)                        
+                        self.label_short.set_markup("<b>Homepage: </b>")                      
+                        self.label_short.set_property("xalign", 0)
+                        
+                        self.vbox.add(self.label_short)
+                        self.vbox.add(self.label_info)
+               
+                ################################# BUGTRACKER ROW + COL ###########################################
+                
+                if bugtracker != 'unknown':
+                        self.label_info = gtk.Label()
+                        self.label_info.set_selectable(True)
+                        self.label_info.set_line_wrap(True)
+                        if len(bugtracker) > 35:
+                                self.label_info.set_markup("<a href=\"" + bugtracker + "\">" + bugtracker[0:35] + "..." + "</a>")
+                        else:
+                                self.label_info.set_markup("<a href=\"" + bugtracker + "\">" + bugtracker[0:60] + "</a>")
+                        self.label_info.set_property("xalign", 0)  
+
+                        self.label_short = gtk.Label()
+                        self.label_short.set_selectable(True)                    
+                        self.label_short.set_line_wrap(True)
+                        self.label_short.set_markup("<b>Bugtracker: </b>")                   
+                        self.label_short.set_property("xalign", 0)
+                        
+                        self.vbox.add(self.label_short)
+                        self.vbox.add(self.label_info)
+
+                ################################# LICENSE ROW + COL ############################################
+                
+                self.label_info = gtk.Label()
+                self.label_info.set_selectable(True)
+                self.label_info.set_line_wrap(True)
+                self.label_info.set_markup(license)
+                self.label_info.set_property("xalign", 0)
+
+                self.label_short = gtk.Label()
+                self.label_short.set_selectable(True)                
+                self.label_short.set_line_wrap(True)                
+                self.label_short.set_markup("<span weight=\"bold\">License: </span>")
+                self.label_short.set_property("xalign", 0)
+                
+                self.vbox.add(self.label_short)
+                self.vbox.add(self.label_info)
+
+                ################################### BINB ROW+COL #############################################
+                
+                if binb != '':
+                        self.label_short = gtk.Label()
+                        self.label_short.set_selectable(True)
+                        self.label_short.set_line_wrap(True)
+                        self.label_short.set_markup("<span weight=\"bold\">Brought in by: </span>")
+                        self.label_short.set_property("xalign", 0)
+                        self.vbox.add(self.label_short)
+                        self.label_info = gtk.Label()
+                        self.label_info.set_selectable(True)
+                        self.label_info.set_width_chars(36)
+                        if len(binb) > 200:
+                            scrolled_window = gtk.ScrolledWindow()
+                            scrolled_window.set_policy(gtk.POLICY_NEVER,gtk.POLICY_ALWAYS)
+                            scrolled_window.set_size_request(100,100)
+                            self.label_info.set_markup(binb)
+                            self.label_info.set_padding(6,6)
+                            self.label_info.set_alignment(0,0)
+                            self.label_info.set_line_wrap(True)
+                            scrolled_window.add_with_viewport(self.label_info)
+                            self.vbox.add(scrolled_window)
+                        else:
+                            self.label_info.set_markup(binb)
+                            self.label_info.set_property("xalign", 0)
+                            self.label_info.set_line_wrap(True) 
+                            self.vbox.add(self.label_info)
+
+                ################################ DESCRIPTION TAG ROW #################################################
+                
+                self.label_short = gtk.Label()
+                self.label_short.set_line_wrap(True)
+                self.label_short.set_markup("<span weight=\"bold\">Description </span>")
+                self.label_short.set_property("xalign", 0)
+                self.vbox.add(self.label_short)
+                
+                ################################ DESCRIPTION INFORMATION ROW ##########################################
+                
+                hbox = gtk.HBox(True,spacing = 0)
+
+                self.label_short = gtk.Label()
+                self.label_short.set_selectable(True)
+                self.label_short.set_width_chars(36)
+                if len(description) > 200:
+                    scrolled_window = gtk.ScrolledWindow()
+                    scrolled_window.set_policy(gtk.POLICY_NEVER,gtk.POLICY_ALWAYS)
+                    scrolled_window.set_size_request(100,100)
+                    self.label_short.set_markup(description)
+                    self.label_short.set_padding(6,6)
+                    self.label_short.set_alignment(0,0)
+                    self.label_short.set_line_wrap(True)
+                    scrolled_window.add_with_viewport(self.label_short)
+                    self.vbox.add(scrolled_window)
+                else:    
+                    self.label_short.set_markup(description)
+                    self.label_short.set_property("xalign", 0)
+                    self.label_short.set_line_wrap(True)
+                    self.vbox.add(self.label_short)
+
+                self.vbox.show_all()
diff --git a/bitbake/lib/bb/ui/crumbs/hig/proxydetailsdialog.py b/bitbake/lib/bb/ui/crumbs/hig/proxydetailsdialog.py
new file mode 100644
index 0000000..69e7dff
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/proxydetailsdialog.py
@@ -0,0 +1,90 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012   Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
+
+"""
+The following are convenience classes for implementing GNOME HIG compliant
+BitBake GUI's
+In summary: spacing = 12px, border-width = 6px
+"""
+
+class ProxyDetailsDialog (CrumbsDialog):
+
+    def __init__(self, title, user, passwd, parent, flags, buttons=None):
+        super(ProxyDetailsDialog, self).__init__(title, parent, flags, buttons)
+        self.connect("response", self.response_cb)
+
+        self.auth = not (user == None or passwd == None or user == "")
+        self.user = user or ""
+        self.passwd = passwd or ""
+
+        # create visual elements on the dialog
+        self.create_visual_elements()
+
+    def create_visual_elements(self):
+        self.auth_checkbox = gtk.CheckButton("Use authentication")
+        self.auth_checkbox.set_tooltip_text("Check this box to set the username and the password")
+        self.auth_checkbox.set_active(self.auth)
+        self.auth_checkbox.connect("toggled", self.auth_checkbox_toggled_cb)
+        self.vbox.pack_start(self.auth_checkbox, expand=False, fill=False)
+
+        hbox = gtk.HBox(False, 6)
+        self.user_label = gtk.Label("Username:")
+        self.user_text = gtk.Entry()
+        self.user_text.set_text(self.user)
+        hbox.pack_start(self.user_label, expand=False, fill=False)
+        hbox.pack_end(self.user_text, expand=False, fill=False)
+        self.vbox.pack_start(hbox, expand=False, fill=False)
+
+        hbox = gtk.HBox(False, 6)
+        self.passwd_label = gtk.Label("Password:")
+        self.passwd_text = gtk.Entry()
+        self.passwd_text.set_text(self.passwd)
+        hbox.pack_start(self.passwd_label, expand=False, fill=False)
+        hbox.pack_end(self.passwd_text, expand=False, fill=False)
+        self.vbox.pack_start(hbox, expand=False, fill=False)
+
+        self.refresh_auth_components()
+        self.show_all()
+
+    def refresh_auth_components(self):
+        self.user_label.set_sensitive(self.auth)
+        self.user_text.set_editable(self.auth)
+        self.user_text.set_sensitive(self.auth)
+        self.passwd_label.set_sensitive(self.auth)
+        self.passwd_text.set_editable(self.auth)
+        self.passwd_text.set_sensitive(self.auth)
+
+    def auth_checkbox_toggled_cb(self, button):
+        self.auth = self.auth_checkbox.get_active()
+        self.refresh_auth_components()
+
+    def response_cb(self, dialog, response_id):
+        if response_id == gtk.RESPONSE_OK:
+            if self.auth:
+                self.user = self.user_text.get_text()
+                self.passwd = self.passwd_text.get_text()
+            else:
+                self.user = None
+                self.passwd = None
diff --git a/bitbake/lib/bb/ui/crumbs/hig/retrieveimagedialog.py b/bitbake/lib/bb/ui/crumbs/hig/retrieveimagedialog.py
new file mode 100644
index 0000000..9017139
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/retrieveimagedialog.py
@@ -0,0 +1,51 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2013   Intel Corporation
+#
+# Authored by Cristiana Voicu <cristiana.voicu@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+
+class RetrieveImageDialog (gtk.FileChooserDialog):
+    """
+    This class is used to create a dialog that permits to retrieve
+    a custom image saved previously from Hob.
+    """
+    def __init__(self, directory,title, parent, flags, buttons=None):
+        super(RetrieveImageDialog, self).__init__(title, None, gtk.FILE_CHOOSER_ACTION_OPEN,
+            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN, gtk.RESPONSE_OK))
+        self.directory = directory
+
+        # create visual elements on the dialog
+        self.create_visual_elements()
+
+    def create_visual_elements(self):
+        self.set_show_hidden(True)
+        self.set_default_response(gtk.RESPONSE_OK)
+        self.set_current_folder(self.directory)
+
+        vbox = self.get_children()[0].get_children()[0].get_children()[0]
+        for child in vbox.get_children()[0].get_children()[0].get_children()[0].get_children():
+            vbox.get_children()[0].get_children()[0].get_children()[0].remove(child)
+
+        label1 = gtk.Label()
+        label1.set_text("File system" + self.directory)
+        label1.show()
+        vbox.get_children()[0].get_children()[0].get_children()[0].pack_start(label1, expand=False, fill=False, padding=0)
+        vbox.get_children()[0].get_children()[1].get_children()[0].hide()
+
+        self.get_children()[0].get_children()[1].get_children()[0].set_label("Select")
diff --git a/bitbake/lib/bb/ui/crumbs/hig/saveimagedialog.py b/bitbake/lib/bb/ui/crumbs/hig/saveimagedialog.py
new file mode 100644
index 0000000..4195f70
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/saveimagedialog.py
@@ -0,0 +1,159 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2013   Intel Corporation
+#
+# Authored by Cristiana Voicu <cristiana.voicu@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import glib
+from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
+from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog
+from bb.ui.crumbs.hobwidget import HobButton
+
+class SaveImageDialog (CrumbsDialog):
+    """
+    This class is used to create a dialog that permits to save
+    a custom image in a predefined directory.
+    """
+    def __init__(self, directory, name, description, title, parent, flags, buttons=None):
+        super(SaveImageDialog, self).__init__(title, parent, flags, buttons)
+        self.directory = directory
+        self.builder = parent
+        self.name_field = name
+        self.description_field = description
+
+        # create visual elements on the dialog
+        self.create_visual_elements()
+
+    def create_visual_elements(self):
+        self.set_default_response(gtk.RESPONSE_OK)
+        self.vbox.set_border_width(6)
+
+        sub_vbox = gtk.VBox(False, 12)
+        self.vbox.pack_start(sub_vbox, expand=False, fill=False)
+        label = gtk.Label()
+        label.set_alignment(0, 0)
+        label.set_markup("<b>Name</b>")
+        sub_label = gtk.Label()
+        sub_label.set_alignment(0, 0)
+        content = "Image recipe names should be all lowercase and include only alphanumeric\n"
+        content += "characters. The only special character you can use is the ASCII hyphen (-)."
+        sub_label.set_markup(content)
+        self.name_entry = gtk.Entry()
+        self.name_entry.set_text(self.name_field)
+        self.name_entry.set_size_request(350,30)
+        self.name_entry.connect("changed", self.name_entry_changed)
+        sub_vbox.pack_start(label, expand=False, fill=False)
+        sub_vbox.pack_start(sub_label, expand=False, fill=False)
+        sub_vbox.pack_start(self.name_entry, expand=False, fill=False)
+
+        sub_vbox = gtk.VBox(False, 12)
+        self.vbox.pack_start(sub_vbox, expand=False, fill=False)
+        label = gtk.Label()
+        label.set_alignment(0, 0)
+        label.set_markup("<b>Description</b> (optional)")
+        sub_label = gtk.Label()
+        sub_label.set_alignment(0, 0)
+        sub_label.set_markup("The description should be less than 150 characters long.")
+        self.description_entry = gtk.TextView()
+        description_buffer = self.description_entry.get_buffer()
+        description_buffer.set_text(self.description_field)
+        description_buffer.connect("insert-text", self.limit_description_length)
+        self.description_entry.set_wrap_mode(gtk.WRAP_WORD)
+        self.description_entry.set_size_request(350,50)
+        sub_vbox.pack_start(label, expand=False, fill=False)
+        sub_vbox.pack_start(sub_label, expand=False, fill=False)
+        sub_vbox.pack_start(self.description_entry, expand=False, fill=False)
+
+        sub_vbox = gtk.VBox(False, 12)
+        self.vbox.pack_start(sub_vbox, expand=False, fill=False)
+        label = gtk.Label()
+        label.set_alignment(0, 0)
+        label.set_markup("Your image recipe will be saved to:")
+        sub_label = gtk.Label()
+        sub_label.set_alignment(0, 0)
+        sub_label.set_markup(self.directory)
+        sub_vbox.pack_start(label, expand=False, fill=False)
+        sub_vbox.pack_start(sub_label, expand=False, fill=False)
+
+        table = gtk.Table(1, 4, True)
+
+        cancel_button = gtk.Button()
+        cancel_button.set_label("Cancel")
+        cancel_button.connect("clicked", self.cancel_button_cb)
+        cancel_button.set_size_request(110, 30)
+
+        self.save_button = gtk.Button()
+        self.save_button.set_label("Save")
+        self.save_button.connect("clicked", self.save_button_cb)
+        self.save_button.set_size_request(110, 30)
+        if self.name_entry.get_text() == '':
+            self.save_button.set_sensitive(False)
+
+        table.attach(cancel_button, 2, 3, 0, 1)
+        table.attach(self.save_button, 3, 4, 0, 1)
+        self.vbox.pack_end(table, expand=False, fill=False)
+
+        self.show_all()
+
+    def limit_description_length(self, textbuffer, iter, text, length):
+        buffer_bounds = textbuffer.get_bounds()
+        entire_text = textbuffer.get_text(*buffer_bounds)
+        entire_text += text
+        if len(entire_text)>150 or text=="\n":
+            textbuffer.emit_stop_by_name("insert-text")
+
+    def name_entry_changed(self, entry):
+        text = entry.get_text()
+        if text == '':
+            self.save_button.set_sensitive(False)
+        else:
+            self.save_button.set_sensitive(True)
+
+    def cancel_button_cb(self, button):
+        self.destroy()
+
+    def save_button_cb(self, button):
+        text = self.name_entry.get_text()
+        new_text = text.replace("-","")
+        description_buffer = self.description_entry.get_buffer()
+        description = description_buffer.get_text(description_buffer.get_start_iter(),description_buffer.get_end_iter())
+        if new_text.islower() and new_text.isalnum():
+            self.builder.image_details_page.image_saved = True
+            self.builder.customized = False
+            self.builder.generate_new_image(self.directory+text, description)
+            self.builder.recipe_model.set_in_list(text, description)
+            self.builder.recipe_model.set_selected_image(text)
+            self.builder.image_details_page.show_page(self.builder.IMAGE_GENERATED)
+            self.builder.image_details_page.name_field_template = text
+            self.builder.image_details_page.description_field_template = description
+            self.destroy()
+        else:
+            self.show_invalid_input_error_dialog()
+
+    def show_invalid_input_error_dialog(self):
+        lbl = "<b>Invalid characters in image recipe name</b>"
+        msg = "Image recipe names should be all lowercase and\n"
+        msg += "include only alphanumeric characters. The only\n"
+        msg += "special character you can use is the ASCII hyphen (-)."
+        dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_ERROR, msg)
+        button = dialog.add_button("Close", gtk.RESPONSE_OK)
+        HobButton.style_button(button)
+
+        res = dialog.run()
+        self.name_entry.grab_focus()
+        dialog.destroy()
diff --git a/bitbake/lib/bb/ui/crumbs/hig/settingsuihelper.py b/bitbake/lib/bb/ui/crumbs/hig/settingsuihelper.py
new file mode 100644
index 0000000..e0285c9
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/settingsuihelper.py
@@ -0,0 +1,122 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012   Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import os
+from bb.ui.crumbs.hobwidget import HobInfoButton, HobButton, HobAltButton
+
+"""
+The following are convenience classes for implementing GNOME HIG compliant
+BitBake GUI's
+In summary: spacing = 12px, border-width = 6px
+"""
+
+class SettingsUIHelper():
+
+    def gen_label_widget(self, content):
+        label = gtk.Label()
+        label.set_alignment(0, 0)
+        label.set_markup(content)
+        label.show()
+        return label
+
+    def gen_label_info_widget(self, content, tooltip):
+        table = gtk.Table(1, 10, False)
+        label = self.gen_label_widget(content)
+        info = HobInfoButton(tooltip, self)
+        table.attach(label, 0, 1, 0, 1, xoptions=gtk.FILL)
+        table.attach(info, 1, 2, 0, 1, xoptions=gtk.FILL, xpadding=10)
+        return table
+
+    def gen_spinner_widget(self, content, lower, upper, tooltip=""):
+        hbox = gtk.HBox(False, 12)
+        adjust = gtk.Adjustment(value=content, lower=lower, upper=upper, step_incr=1)
+        spinner = gtk.SpinButton(adjustment=adjust, climb_rate=1, digits=0)
+
+        spinner.set_value(content)
+        hbox.pack_start(spinner, expand=False, fill=False)
+
+        info = HobInfoButton(tooltip, self)
+        hbox.pack_start(info, expand=False, fill=False)
+
+        hbox.show_all()
+        return hbox, spinner
+
+    def gen_combo_widget(self, curr_item, all_item, tooltip=""):
+        hbox = gtk.HBox(False, 12)
+        combo = gtk.combo_box_new_text()
+        hbox.pack_start(combo, expand=False, fill=False)
+
+        index = 0
+        for item in all_item or []:
+            combo.append_text(item)
+            if item == curr_item:
+                combo.set_active(index)
+            index += 1
+
+        info = HobInfoButton(tooltip, self)
+        hbox.pack_start(info, expand=False, fill=False)
+
+        hbox.show_all()
+        return hbox, combo
+
+    def entry_widget_select_path_cb(self, action, parent, entry):
+        dialog = gtk.FileChooserDialog("", parent,
+                                       gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
+        text = entry.get_text()
+        dialog.set_current_folder(text if len(text) > 0 else os.getcwd())
+        button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
+        HobAltButton.style_button(button)
+        button = dialog.add_button("Open", gtk.RESPONSE_YES)
+        HobButton.style_button(button)
+        response = dialog.run()
+        if response == gtk.RESPONSE_YES:
+            path = dialog.get_filename()
+            entry.set_text(path)
+
+        dialog.destroy()
+
+    def gen_entry_widget(self, content, parent, tooltip="", need_button=True):
+        hbox = gtk.HBox(False, 12)
+        entry = gtk.Entry()
+        entry.set_text(content)
+        entry.set_size_request(350,30)
+
+        if need_button:
+            table = gtk.Table(1, 10, False)
+            hbox.pack_start(table, expand=True, fill=True)
+            table.attach(entry, 0, 9, 0, 1, xoptions=gtk.SHRINK)
+            image = gtk.Image()
+            image.set_from_stock(gtk.STOCK_OPEN,gtk.ICON_SIZE_BUTTON)
+            open_button = gtk.Button()
+            open_button.set_image(image)
+            open_button.connect("clicked", self.entry_widget_select_path_cb, parent, entry)
+            table.attach(open_button, 9, 10, 0, 1, xoptions=gtk.SHRINK)
+        else:
+            hbox.pack_start(entry, expand=True, fill=True)
+
+        if tooltip != "":
+            info = HobInfoButton(tooltip, self)
+            hbox.pack_start(info, expand=False, fill=False)
+
+        hbox.show_all()
+        return hbox, entry
diff --git a/bitbake/lib/bb/ui/crumbs/hig/simplesettingsdialog.py b/bitbake/lib/bb/ui/crumbs/hig/simplesettingsdialog.py
new file mode 100644
index 0000000..b5eb3d8
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hig/simplesettingsdialog.py
@@ -0,0 +1,891 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012   Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import gobject
+import hashlib
+from bb.ui.crumbs.hobwidget import hic, HobInfoButton, HobButton, HobAltButton 
+from bb.ui.crumbs.progressbar import HobProgressBar
+from bb.ui.crumbs.hig.settingsuihelper import SettingsUIHelper
+from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
+from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog
+from bb.ui.crumbs.hig.proxydetailsdialog import ProxyDetailsDialog
+
+"""
+The following are convenience classes for implementing GNOME HIG compliant
+BitBake GUI's
+In summary: spacing = 12px, border-width = 6px
+"""
+
+class SimpleSettingsDialog (CrumbsDialog, SettingsUIHelper):
+
+    (BUILD_ENV_PAGE_ID,
+     SHARED_STATE_PAGE_ID,
+     PROXIES_PAGE_ID,
+     OTHERS_PAGE_ID) = range(4)
+
+    (TEST_NETWORK_NONE,
+     TEST_NETWORK_INITIAL,
+     TEST_NETWORK_RUNNING,
+     TEST_NETWORK_PASSED,
+     TEST_NETWORK_FAILED,
+     TEST_NETWORK_CANCELED) = range(6)
+
+    TARGETS = [
+        ("MY_TREE_MODEL_ROW", gtk.TARGET_SAME_WIDGET, 0),
+        ("text/plain", 0, 1),
+        ("TEXT", 0, 2),
+        ("STRING", 0, 3),
+        ]
+
+    def __init__(self, title, configuration, all_image_types,
+            all_package_formats, all_distros, all_sdk_machines,
+            max_threads, parent, flags, handler, buttons=None):
+        super(SimpleSettingsDialog, self).__init__(title, parent, flags, buttons)
+
+        # class members from other objects
+        # bitbake settings from Builder.Configuration
+        self.configuration = configuration
+        self.image_types = all_image_types
+        self.all_package_formats = all_package_formats
+        self.all_distros = all_distros
+        self.all_sdk_machines = all_sdk_machines
+        self.max_threads = max_threads
+
+        # class members for internal use
+        self.dldir_text = None
+        self.sstatedir_text = None
+        self.sstatemirrors_list = []
+        self.sstatemirrors_changed = 0
+        self.bb_spinner = None
+        self.pmake_spinner = None
+        self.rootfs_size_spinner = None
+        self.extra_size_spinner = None
+        self.gplv3_checkbox = None
+        self.toolchain_checkbox = None
+        self.setting_store = None
+        self.image_types_checkbuttons = {}
+
+        self.md5 = self.config_md5()
+        self.proxy_md5 = self.config_proxy_md5()
+        self.settings_changed = False
+        self.proxy_settings_changed = False
+        self.handler = handler
+        self.proxy_test_ran = False
+        self.selected_mirror_row = 0
+        self.new_mirror = False
+
+        # create visual elements on the dialog
+        self.create_visual_elements()
+        self.connect("response", self.response_cb)
+
+    def _get_sorted_value(self, var):
+        return " ".join(sorted(str(var).split())) + "\n"
+
+    def config_proxy_md5(self):
+        data = ("ENABLE_PROXY: "         + self._get_sorted_value(self.configuration.enable_proxy))
+        if self.configuration.enable_proxy:
+            for protocol in self.configuration.proxies.keys():
+                data += (protocol + ": " + self._get_sorted_value(self.configuration.combine_proxy(protocol)))
+        return hashlib.md5(data).hexdigest()
+
+    def config_md5(self):
+        data = ""
+        for key in self.configuration.extra_setting.keys():
+            data += (key + ": " + self._get_sorted_value(self.configuration.extra_setting[key]))
+        return hashlib.md5(data).hexdigest()
+
+    def gen_proxy_entry_widget(self, protocol, parent, need_button=True, line=0):
+        label = gtk.Label(protocol.upper() + " proxy")
+        self.proxy_table.attach(label, 0, 1, line, line+1, xpadding=24)
+
+        proxy_entry = gtk.Entry()
+        proxy_entry.set_size_request(300, -1)
+        self.proxy_table.attach(proxy_entry, 1, 2, line, line+1, ypadding=4)
+
+        self.proxy_table.attach(gtk.Label(":"), 2, 3, line, line+1, xpadding=12, ypadding=4)
+
+        port_entry = gtk.Entry()
+        port_entry.set_size_request(60, -1)
+        self.proxy_table.attach(port_entry, 3, 4, line, line+1, ypadding=4)
+
+        details_button = HobAltButton("Details")
+        details_button.connect("clicked", self.details_cb, parent, protocol)
+        self.proxy_table.attach(details_button, 4, 5, line, line+1, xpadding=4, yoptions=gtk.EXPAND)
+
+        return proxy_entry, port_entry, details_button
+
+    def refresh_proxy_components(self):
+        self.same_checkbox.set_sensitive(self.configuration.enable_proxy)
+
+        self.http_proxy.set_text(self.configuration.combine_host_only("http"))
+        self.http_proxy.set_editable(self.configuration.enable_proxy)
+        self.http_proxy.set_sensitive(self.configuration.enable_proxy)
+        self.http_proxy_port.set_text(self.configuration.combine_port_only("http"))
+        self.http_proxy_port.set_editable(self.configuration.enable_proxy)
+        self.http_proxy_port.set_sensitive(self.configuration.enable_proxy)
+        self.http_proxy_details.set_sensitive(self.configuration.enable_proxy)
+
+        self.https_proxy.set_text(self.configuration.combine_host_only("https"))
+        self.https_proxy.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+        self.https_proxy.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+        self.https_proxy_port.set_text(self.configuration.combine_port_only("https"))
+        self.https_proxy_port.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+        self.https_proxy_port.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+        self.https_proxy_details.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+
+        self.ftp_proxy.set_text(self.configuration.combine_host_only("ftp"))
+        self.ftp_proxy.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+        self.ftp_proxy.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+        self.ftp_proxy_port.set_text(self.configuration.combine_port_only("ftp"))
+        self.ftp_proxy_port.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+        self.ftp_proxy_port.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+        self.ftp_proxy_details.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+
+        self.socks_proxy.set_text(self.configuration.combine_host_only("socks"))
+        self.socks_proxy.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+        self.socks_proxy.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+        self.socks_proxy_port.set_text(self.configuration.combine_port_only("socks"))
+        self.socks_proxy_port.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+        self.socks_proxy_port.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+        self.socks_proxy_details.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+
+        self.cvs_proxy.set_text(self.configuration.combine_host_only("cvs"))
+        self.cvs_proxy.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+        self.cvs_proxy.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+        self.cvs_proxy_port.set_text(self.configuration.combine_port_only("cvs"))
+        self.cvs_proxy_port.set_editable(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+        self.cvs_proxy_port.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+        self.cvs_proxy_details.set_sensitive(self.configuration.enable_proxy and (not self.configuration.same_proxy))
+
+        if self.configuration.same_proxy:
+            if self.http_proxy.get_text():
+                [w.set_text(self.http_proxy.get_text()) for w in self.same_proxy_addresses]
+            if self.http_proxy_port.get_text():
+                [w.set_text(self.http_proxy_port.get_text()) for w in self.same_proxy_ports]
+
+    def proxy_checkbox_toggled_cb(self, button):
+        self.configuration.enable_proxy = self.proxy_checkbox.get_active()
+        if not self.configuration.enable_proxy:
+            self.configuration.same_proxy = False
+            self.same_checkbox.set_active(self.configuration.same_proxy)
+        self.save_proxy_data()
+        self.refresh_proxy_components()
+
+    def same_checkbox_toggled_cb(self, button):
+        self.configuration.same_proxy = self.same_checkbox.get_active()
+        self.save_proxy_data()
+        self.refresh_proxy_components()
+
+    def save_proxy_data(self):
+        self.configuration.split_proxy("http", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text())
+        if self.configuration.same_proxy:
+            self.configuration.split_proxy("https", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text())
+            self.configuration.split_proxy("ftp", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text())
+            self.configuration.split_proxy("socks", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text())
+            self.configuration.split_proxy("cvs", self.http_proxy.get_text() + ":" + self.http_proxy_port.get_text())
+        else:
+            self.configuration.split_proxy("https", self.https_proxy.get_text() + ":" + self.https_proxy_port.get_text())
+            self.configuration.split_proxy("ftp", self.ftp_proxy.get_text() + ":" + self.ftp_proxy_port.get_text())
+            self.configuration.split_proxy("socks", self.socks_proxy.get_text() + ":" + self.socks_proxy_port.get_text())
+            self.configuration.split_proxy("cvs", self.cvs_proxy.get_text() + ":" + self.cvs_proxy_port.get_text())       
+
+    def response_cb(self, dialog, response_id):
+        if response_id == gtk.RESPONSE_YES:
+            if self.proxy_checkbox.get_active():
+                # Check that all proxy entries have a corresponding port
+                for proxy, port in zip(self.all_proxy_addresses, self.all_proxy_ports):
+                    if proxy.get_text() and not port.get_text():
+                        lbl = "<b>Enter all port numbers</b>"
+                        msg = "Proxy servers require a port number. Please make sure you have entered a port number for each proxy server."
+                        dialog = CrumbsMessageDialog(self, lbl, gtk.MESSAGE_WARNING, msg)
+                        button = dialog.add_button("Close", gtk.RESPONSE_OK)
+                        HobButton.style_button(button)
+                        response = dialog.run()
+                        dialog.destroy()
+                        self.emit_stop_by_name("response")
+                        return
+
+        self.configuration.dldir = self.dldir_text.get_text()
+        self.configuration.sstatedir = self.sstatedir_text.get_text()
+        self.configuration.sstatemirror = ""
+        for mirror in self.sstatemirrors_list:
+            if mirror[1] != "" and mirror[2].startswith("file://"):
+                smirror = mirror[2] + " " + mirror[1] + " \\n "
+                self.configuration.sstatemirror += smirror
+        self.configuration.bbthread = self.bb_spinner.get_value_as_int()
+        self.configuration.pmake = self.pmake_spinner.get_value_as_int()
+        self.save_proxy_data()
+        self.configuration.extra_setting = {}
+        it = self.setting_store.get_iter_first()
+        while it:
+            key = self.setting_store.get_value(it, 0)
+            value = self.setting_store.get_value(it, 1)
+            self.configuration.extra_setting[key] = value
+            it = self.setting_store.iter_next(it)
+
+        md5 = self.config_md5()
+        self.settings_changed = (self.md5 != md5)
+        self.proxy_settings_changed = (self.proxy_md5 != self.config_proxy_md5())
+
+    def create_build_environment_page(self):
+        advanced_vbox = gtk.VBox(False, 6)
+        advanced_vbox.set_border_width(6)
+
+        advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Parallel threads</span>'), expand=False, fill=False)
+        sub_vbox = gtk.VBox(False, 6)
+        advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
+        label = self.gen_label_widget("BitBake parallel threads")
+        tooltip = "Sets the number of threads that BitBake tasks can simultaneously run. See the <a href=\""
+        tooltip += "http://www.yoctoproject.org/docs/current/poky-ref-manual/"
+        tooltip += "poky-ref-manual.html#var-BB_NUMBER_THREADS\">Poky reference manual</a> for information"
+        bbthread_widget, self.bb_spinner = self.gen_spinner_widget(self.configuration.bbthread, 1, self.max_threads,"<b>BitBake prallalel threads</b>" + "*" + tooltip)
+        sub_vbox.pack_start(label, expand=False, fill=False)
+        sub_vbox.pack_start(bbthread_widget, expand=False, fill=False)
+
+        sub_vbox = gtk.VBox(False, 6)
+        advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
+        label = self.gen_label_widget("Make parallel threads")
+        tooltip = "Sets the maximum number of threads the host can use during the build. See the <a href=\""
+        tooltip += "http://www.yoctoproject.org/docs/current/poky-ref-manual/"
+        tooltip += "poky-ref-manual.html#var-PARALLEL_MAKE\">Poky reference manual</a> for information"
+        pmake_widget, self.pmake_spinner = self.gen_spinner_widget(self.configuration.pmake, 1, self.max_threads,"<b>Make parallel threads</b>" + "*" + tooltip)
+        sub_vbox.pack_start(label, expand=False, fill=False)
+        sub_vbox.pack_start(pmake_widget, expand=False, fill=False)
+
+        advanced_vbox.pack_start(self.gen_label_widget('<span weight="bold">Downloaded source code</span>'), expand=False, fill=False)
+        sub_vbox = gtk.VBox(False, 6)
+        advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
+        label = self.gen_label_widget("Downloads directory")
+        tooltip = "Select a folder that caches the upstream project source code"
+        dldir_widget, self.dldir_text = self.gen_entry_widget(self.configuration.dldir, self,"<b>Downloaded source code</b>" + "*" + tooltip)
+        sub_vbox.pack_start(label, expand=False, fill=False)
+        sub_vbox.pack_start(dldir_widget, expand=False, fill=False)
+
+        return advanced_vbox
+
+    def create_shared_state_page(self):
+        advanced_vbox = gtk.VBox(False)
+        advanced_vbox.set_border_width(12)
+
+        sub_vbox = gtk.VBox(False)
+        advanced_vbox.pack_start(sub_vbox, expand=False, fill=False, padding=24)
+        content = "<span>Shared state directory</span>"
+        tooltip = "Select a folder that caches your prebuilt results"
+        label = self.gen_label_info_widget(content,"<b>Shared state directory</b>" + "*" + tooltip)
+        sstatedir_widget, self.sstatedir_text = self.gen_entry_widget(self.configuration.sstatedir, self)
+        sub_vbox.pack_start(label, expand=False, fill=False)
+        sub_vbox.pack_start(sstatedir_widget, expand=False, fill=False, padding=6)
+
+        content = "<span weight=\"bold\">Shared state mirrors</span>"
+        tooltip = "URLs pointing to pre-built mirrors that will speed your build. "
+        tooltip += "Select the \'Standard\' configuration if the structure of your "
+        tooltip += "mirror replicates the structure of your local shared state directory. "
+        tooltip += "For more information on shared state mirrors, check the <a href=\""
+        tooltip += "http://www.yoctoproject.org/docs/current/poky-ref-manual/"
+        tooltip += "poky-ref-manual.html#shared-state\">Yocto Project Reference Manual</a>."
+        table = self.gen_label_info_widget(content,"<b>Shared state mirrors</b>" + "*" + tooltip)
+        advanced_vbox.pack_start(table, expand=False, fill=False, padding=6)
+
+        sub_vbox = gtk.VBox(False)
+        advanced_vbox.pack_start(sub_vbox, gtk.TRUE, gtk.TRUE, 0)
+
+        if self.sstatemirrors_changed == 0:
+            self.sstatemirrors_changed = 1
+            sstatemirrors = self.configuration.sstatemirror
+            if sstatemirrors == "":
+                sm_list = ["Standard", "", "file://(.*)"]
+                self.sstatemirrors_list.append(sm_list)
+            else:
+                sstatemirrors = [x for x in sstatemirrors.split('\\n')]
+                for sstatemirror in sstatemirrors:
+                    sstatemirror_fields = [x for x in sstatemirror.split(' ') if x.strip()]
+                    if len(sstatemirror_fields) == 2:
+                        if sstatemirror_fields[0] == "file://(.*)" or sstatemirror_fields[0] == "file://.*":
+                            sm_list = ["Standard", sstatemirror_fields[1], sstatemirror_fields[0]]
+                        else:
+                            sm_list = ["Custom", sstatemirror_fields[1], sstatemirror_fields[0]]
+                        self.sstatemirrors_list.append(sm_list)
+
+        sstatemirrors_widget, sstatemirrors_store = self.gen_shared_sstate_widget(self.sstatemirrors_list, self)
+        sub_vbox.pack_start(sstatemirrors_widget, expand=True, fill=True)
+
+        table = gtk.Table(1, 10, False)
+        table.set_col_spacings(6)
+        add_mirror_button = HobAltButton("Add mirror")
+        add_mirror_button.connect("clicked", self.add_mirror)
+        add_mirror_button.set_size_request(120,30)
+        table.attach(add_mirror_button, 1, 2, 0, 1, xoptions=gtk.SHRINK)
+
+        self.delete_button = HobAltButton("Delete mirror")
+        self.delete_button.connect("clicked", self.delete_cb)
+        self.delete_button.set_size_request(120, 30)
+        table.attach(self.delete_button, 3, 4, 0, 1, xoptions=gtk.SHRINK)
+
+        advanced_vbox.pack_start(table, expand=False, fill=False, padding=6)
+
+        return advanced_vbox
+
+    def gen_shared_sstate_widget(self, sstatemirrors_list, window):
+        hbox = gtk.HBox(False)
+
+        sstatemirrors_store = gtk.ListStore(str, str, str)
+        for sstatemirror in sstatemirrors_list:
+            sstatemirrors_store.append(sstatemirror)
+
+        self.sstatemirrors_tv = gtk.TreeView()
+        self.sstatemirrors_tv.set_rules_hint(True)
+        self.sstatemirrors_tv.set_headers_visible(True)
+        tree_selection = self.sstatemirrors_tv.get_selection()
+        tree_selection.set_mode(gtk.SELECTION_SINGLE)
+
+        # Allow enable drag and drop of rows including row move
+        self.sstatemirrors_tv.enable_model_drag_source( gtk.gdk.BUTTON1_MASK,
+            self.TARGETS,
+            gtk.gdk.ACTION_DEFAULT|
+            gtk.gdk.ACTION_MOVE)
+        self.sstatemirrors_tv.enable_model_drag_dest(self.TARGETS,
+            gtk.gdk.ACTION_DEFAULT)
+        self.sstatemirrors_tv.connect("drag_data_get", self.drag_data_get_cb)
+        self.sstatemirrors_tv.connect("drag_data_received", self.drag_data_received_cb)
+
+
+        self.scroll = gtk.ScrolledWindow()
+        self.scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+        self.scroll.set_shadow_type(gtk.SHADOW_IN)
+        self.scroll.connect('size-allocate', self.scroll_changed)
+        self.scroll.add(self.sstatemirrors_tv)
+
+        #list store for cell renderer
+        m = gtk.ListStore(gobject.TYPE_STRING)
+        m.append(["Standard"])
+        m.append(["Custom"])
+
+        cell0 = gtk.CellRendererCombo()
+        cell0.set_property("model",m)
+        cell0.set_property("text-column", 0)
+        cell0.set_property("editable", True)
+        cell0.set_property("has-entry", False)
+        col0 = gtk.TreeViewColumn("Configuration")
+        col0.pack_start(cell0, False)
+        col0.add_attribute(cell0, "text", 0)
+        col0.set_cell_data_func(cell0, self.configuration_field)
+        self.sstatemirrors_tv.append_column(col0)
+
+        cell0.connect("edited", self.combo_changed, sstatemirrors_store)
+
+        self.cell1 = gtk.CellRendererText()
+        self.cell1.set_padding(5,2)
+        col1 = gtk.TreeViewColumn('Regex', self.cell1)
+        col1.set_cell_data_func(self.cell1, self.regex_field)
+        self.sstatemirrors_tv.append_column(col1)
+
+        self.cell1.connect("edited", self.regex_changed, sstatemirrors_store)
+
+        cell2 = gtk.CellRendererText()
+        cell2.set_padding(5,2)
+        cell2.set_property("editable", True)
+        col2 = gtk.TreeViewColumn('URL', cell2)
+        col2.set_cell_data_func(cell2, self.url_field)
+        self.sstatemirrors_tv.append_column(col2)
+
+        cell2.connect("edited", self.url_changed, sstatemirrors_store)
+
+        self.sstatemirrors_tv.set_model(sstatemirrors_store)
+        self.sstatemirrors_tv.set_cursor(self.selected_mirror_row)
+        hbox.pack_start(self.scroll, expand=True, fill=True)
+        hbox.show_all()
+
+        return hbox, sstatemirrors_store
+
+    def drag_data_get_cb(self, treeview, context, selection, target_id, etime):
+        treeselection = treeview.get_selection()
+        model, iter = treeselection.get_selected()
+        data = model.get_string_from_iter(iter)
+        selection.set(selection.target, 8, data)
+
+    def drag_data_received_cb(self, treeview, context, x, y, selection, info, etime):
+        model = treeview.get_model()
+        data = []
+        tree_iter = model.get_iter_from_string(selection.data)
+        data.append(model.get_value(tree_iter, 0))
+        data.append(model.get_value(tree_iter, 1))
+        data.append(model.get_value(tree_iter, 2))
+
+        drop_info = treeview.get_dest_row_at_pos(x, y)
+        if drop_info:
+            path, position = drop_info
+            iter = model.get_iter(path)
+            if (position == gtk.TREE_VIEW_DROP_BEFORE or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
+                model.insert_before(iter, data)
+            else:
+                model.insert_after(iter, data)
+        else:
+            model.append(data)
+        if context.action == gtk.gdk.ACTION_MOVE:
+            context.finish(True, True, etime)
+        return
+
+    def delete_cb(self, button):
+        selection = self.sstatemirrors_tv.get_selection()
+        tree_model, tree_iter = selection.get_selected()
+        index = int(tree_model.get_string_from_iter(tree_iter))
+        if index == 0:
+            self.selected_mirror_row = index
+        else:
+            self.selected_mirror_row = index - 1
+        self.sstatemirrors_list.pop(index)
+        self.refresh_shared_state_page()
+        if not self.sstatemirrors_list:
+            self.delete_button.set_sensitive(False)
+
+    def add_mirror(self, button):
+        self.new_mirror = True
+        tooltip = "Select the pre-built mirror that will speed your build"
+        index = len(self.sstatemirrors_list)
+        self.selected_mirror_row = index
+        sm_list = ["Standard", "", "file://(.*)"]
+        self.sstatemirrors_list.append(sm_list)
+        self.refresh_shared_state_page()
+
+    def scroll_changed(self, widget, event, data=None):
+        if self.new_mirror == True:
+            adj = widget.get_vadjustment()
+            adj.set_value(adj.upper - adj.page_size)
+            self.new_mirror = False
+
+    def combo_changed(self, widget, path, text, model):
+        model[path][0] = text
+        selection = self.sstatemirrors_tv.get_selection()
+        tree_model, tree_iter = selection.get_selected()
+        index = int(tree_model.get_string_from_iter(tree_iter))
+        self.sstatemirrors_list[index][0] = text
+
+    def regex_changed(self, cell, path, new_text, user_data):
+        user_data[path][2] = new_text
+        selection = self.sstatemirrors_tv.get_selection()
+        tree_model, tree_iter = selection.get_selected()
+        index = int(tree_model.get_string_from_iter(tree_iter))
+        self.sstatemirrors_list[index][2] = new_text
+        return
+
+    def url_changed(self, cell, path, new_text, user_data):
+        if new_text!="Enter the mirror URL" and new_text!="Match regex and replace it with this URL":
+            user_data[path][1] = new_text
+            selection = self.sstatemirrors_tv.get_selection()
+            tree_model, tree_iter = selection.get_selected()
+            index = int(tree_model.get_string_from_iter(tree_iter))
+            self.sstatemirrors_list[index][1] = new_text
+        return
+
+    def configuration_field(self, column, cell, model, iter):
+        cell.set_property('text', model.get_value(iter, 0))
+        if model.get_value(iter, 0) == "Standard":
+            self.cell1.set_property("sensitive", False)
+            self.cell1.set_property("editable", False)
+        else:
+            self.cell1.set_property("sensitive", True)
+            self.cell1.set_property("editable", True)
+        return
+
+    def regex_field(self, column, cell, model, iter):
+        cell.set_property('text', model.get_value(iter, 2))
+        return
+
+    def url_field(self, column, cell, model, iter):
+        text = model.get_value(iter, 1)
+        if text == "":
+            if model.get_value(iter, 0) == "Standard":
+                text = "Enter the mirror URL"
+            else:
+                text = "Match regex and replace it with this URL"
+        cell.set_property('text', text)
+        return
+
+    def refresh_shared_state_page(self):
+        page_num = self.nb.get_current_page()
+        self.nb.remove_page(page_num);
+        self.nb.insert_page(self.create_shared_state_page(), gtk.Label("Shared state"),page_num)
+        self.show_all()
+        self.nb.set_current_page(page_num)
+
+    def test_proxy_ended(self, passed):
+        self.proxy_test_running = False
+        self.set_test_proxy_state(self.TEST_NETWORK_PASSED if passed else self.TEST_NETWORK_FAILED)
+        self.set_sensitive(True)
+        self.refresh_proxy_components()
+
+    def timer_func(self):
+        self.test_proxy_progress.pulse()
+        return self.proxy_test_running
+
+    def test_network_button_cb(self, b):
+        self.set_test_proxy_state(self.TEST_NETWORK_RUNNING)
+        self.set_sensitive(False)
+        self.save_proxy_data()
+        if self.configuration.enable_proxy == True:
+            self.handler.set_http_proxy(self.configuration.combine_proxy("http"))
+            self.handler.set_https_proxy(self.configuration.combine_proxy("https"))
+            self.handler.set_ftp_proxy(self.configuration.combine_proxy("ftp"))
+            self.handler.set_socks_proxy(self.configuration.combine_proxy("socks"))
+            self.handler.set_cvs_proxy(self.configuration.combine_host_only("cvs"), self.configuration.combine_port_only("cvs"))
+        elif self.configuration.enable_proxy == False:
+            self.handler.set_http_proxy("")
+            self.handler.set_https_proxy("")
+            self.handler.set_ftp_proxy("")
+            self.handler.set_socks_proxy("")
+            self.handler.set_cvs_proxy("", "")
+        self.proxy_test_ran = True
+        self.proxy_test_running = True
+        gobject.timeout_add(100, self.timer_func)
+        self.handler.trigger_network_test()
+
+    def test_proxy_focus_event(self, w, direction):
+        if self.test_proxy_state in [self.TEST_NETWORK_PASSED, self.TEST_NETWORK_FAILED]:
+            self.set_test_proxy_state(self.TEST_NETWORK_INITIAL)
+        return False
+
+    def http_proxy_changed(self, e):
+        if not self.configuration.same_proxy:
+            return
+        if e == self.http_proxy:
+            [w.set_text(self.http_proxy.get_text()) for w in self.same_proxy_addresses]
+        else:
+            [w.set_text(self.http_proxy_port.get_text()) for w in self.same_proxy_ports]
+
+    def proxy_address_focus_out_event(self, w, direction):
+        text = w.get_text()
+        if not text:
+            return False
+        if text.find("//") == -1:
+            w.set_text("http://" + text)
+        return False
+
+    def set_test_proxy_state(self, state):
+        if self.test_proxy_state == state:
+            return
+        [self.proxy_table.remove(w) for w in self.test_gui_elements]
+        if state == self.TEST_NETWORK_INITIAL:
+            self.proxy_table.attach(self.test_network_button, 1, 2, 5, 6)
+            self.test_network_button.show()
+        elif state == self.TEST_NETWORK_RUNNING:
+            self.test_proxy_progress.set_rcstyle("running")
+            self.test_proxy_progress.set_text("Testing network configuration")
+            self.proxy_table.attach(self.test_proxy_progress, 0, 5, 5, 6, xpadding=4)
+            self.test_proxy_progress.show()
+        else: # passed or failed
+            self.dummy_progress.update(1.0)
+            if state == self.TEST_NETWORK_PASSED:
+                self.dummy_progress.set_text("Your network is properly configured")
+                self.dummy_progress.set_rcstyle("running")
+            else:
+                self.dummy_progress.set_text("Network test failed")
+                self.dummy_progress.set_rcstyle("fail")
+            self.proxy_table.attach(self.dummy_progress, 0, 4, 5, 6)
+            self.proxy_table.attach(self.retest_network_button, 4, 5, 5, 6, xpadding=4)
+            self.dummy_progress.show()
+            self.retest_network_button.show()
+        self.test_proxy_state = state
+
+    def create_network_page(self):
+        advanced_vbox = gtk.VBox(False, 6)
+        advanced_vbox.set_border_width(6)
+        self.same_proxy_addresses = []
+        self.same_proxy_ports = []
+        self.all_proxy_ports = []
+        self.all_proxy_addresses = []
+
+        sub_vbox = gtk.VBox(False, 6)
+        advanced_vbox.pack_start(sub_vbox, expand=False, fill=False)
+        label = self.gen_label_widget("<span weight=\"bold\">Set the proxies used when fetching source code</span>")
+        tooltip = "Set the proxies used when fetching source code.  A blank field uses a direct internet connection."
+        info = HobInfoButton("<span weight=\"bold\">Set the proxies used when fetching source code</span>" + "*" + tooltip, self)
+        hbox = gtk.HBox(False, 12)
+        hbox.pack_start(label, expand=True, fill=True)
+        hbox.pack_start(info, expand=False, fill=False)
+        sub_vbox.pack_start(hbox, expand=False, fill=False)
+
+        proxy_test_focus = []
+        self.direct_checkbox = gtk.RadioButton(None, "Direct network connection")
+        proxy_test_focus.append(self.direct_checkbox)
+        self.direct_checkbox.set_tooltip_text("Check this box to use a direct internet connection with no proxy")
+        self.direct_checkbox.set_active(not self.configuration.enable_proxy)
+        sub_vbox.pack_start(self.direct_checkbox, expand=False, fill=False)
+
+        self.proxy_checkbox = gtk.RadioButton(self.direct_checkbox, "Manual proxy configuration")
+        proxy_test_focus.append(self.proxy_checkbox)
+        self.proxy_checkbox.set_tooltip_text("Check this box to manually set up a specific proxy")
+        self.proxy_checkbox.set_active(self.configuration.enable_proxy)
+        sub_vbox.pack_start(self.proxy_checkbox, expand=False, fill=False)
+
+        self.same_checkbox = gtk.CheckButton("Use the HTTP proxy for all protocols")
+        proxy_test_focus.append(self.same_checkbox)
+        self.same_checkbox.set_tooltip_text("Check this box to use the HTTP proxy for all five proxies")
+        self.same_checkbox.set_active(self.configuration.same_proxy)
+        hbox = gtk.HBox(False, 12)
+        hbox.pack_start(self.same_checkbox, expand=False, fill=False, padding=24)
+        sub_vbox.pack_start(hbox, expand=False, fill=False)
+
+        self.proxy_table = gtk.Table(6, 5, False)
+        self.http_proxy, self.http_proxy_port, self.http_proxy_details = self.gen_proxy_entry_widget(
+            "http", self, True, 0)
+        proxy_test_focus +=[self.http_proxy, self.http_proxy_port]
+        self.http_proxy.connect("changed", self.http_proxy_changed)
+        self.http_proxy_port.connect("changed", self.http_proxy_changed)
+
+        self.https_proxy, self.https_proxy_port, self.https_proxy_details = self.gen_proxy_entry_widget(
+            "https", self, True, 1)
+        proxy_test_focus += [self.https_proxy, self.https_proxy_port]
+        self.same_proxy_addresses.append(self.https_proxy)
+        self.same_proxy_ports.append(self.https_proxy_port)
+
+        self.ftp_proxy, self.ftp_proxy_port, self.ftp_proxy_details = self.gen_proxy_entry_widget(
+            "ftp", self, True, 2)
+        proxy_test_focus += [self.ftp_proxy, self.ftp_proxy_port]
+        self.same_proxy_addresses.append(self.ftp_proxy)
+        self.same_proxy_ports.append(self.ftp_proxy_port)
+
+        self.socks_proxy, self.socks_proxy_port, self.socks_proxy_details = self.gen_proxy_entry_widget(
+            "socks", self, True, 3)
+        proxy_test_focus += [self.socks_proxy, self.socks_proxy_port]
+        self.same_proxy_addresses.append(self.socks_proxy)
+        self.same_proxy_ports.append(self.socks_proxy_port)
+
+        self.cvs_proxy, self.cvs_proxy_port, self.cvs_proxy_details = self.gen_proxy_entry_widget(
+            "cvs", self, True, 4)
+        proxy_test_focus += [self.cvs_proxy, self.cvs_proxy_port]
+        self.same_proxy_addresses.append(self.cvs_proxy)
+        self.same_proxy_ports.append(self.cvs_proxy_port)
+        self.all_proxy_ports = self.same_proxy_ports + [self.http_proxy_port]
+        self.all_proxy_addresses = self.same_proxy_addresses + [self.http_proxy]
+        sub_vbox.pack_start(self.proxy_table, expand=False, fill=False)
+        self.proxy_table.show_all()
+
+        # Create the graphical elements for the network test feature, but don't display them yet
+        self.test_network_button = HobAltButton("Test network configuration")
+        self.test_network_button.connect("clicked", self.test_network_button_cb)
+        self.test_proxy_progress = HobProgressBar()
+        self.dummy_progress = HobProgressBar()
+        self.retest_network_button = HobAltButton("Retest")
+        self.retest_network_button.connect("clicked", self.test_network_button_cb)
+        self.test_gui_elements = [self.test_network_button, self.test_proxy_progress, self.dummy_progress, self.retest_network_button]
+        # Initialize the network tester
+        self.test_proxy_state = self.TEST_NETWORK_NONE
+        self.set_test_proxy_state(self.TEST_NETWORK_INITIAL)
+        self.proxy_test_passed_id = self.handler.connect("network-passed", lambda h:self.test_proxy_ended(True))
+        self.proxy_test_failed_id = self.handler.connect("network-failed", lambda h:self.test_proxy_ended(False))
+        [w.connect("focus-in-event", self.test_proxy_focus_event) for w in proxy_test_focus]
+        [w.connect("focus-out-event", self.proxy_address_focus_out_event) for w in self.all_proxy_addresses]
+
+        self.direct_checkbox.connect("toggled", self.proxy_checkbox_toggled_cb)
+        self.proxy_checkbox.connect("toggled", self.proxy_checkbox_toggled_cb)
+        self.same_checkbox.connect("toggled", self.same_checkbox_toggled_cb)
+
+        self.refresh_proxy_components()
+        return advanced_vbox
+
+    def switch_to_page(self, page_id):
+        self.nb.set_current_page(page_id)
+
+    def details_cb(self, button, parent, protocol):
+        self.save_proxy_data()
+        dialog = ProxyDetailsDialog(title = protocol.upper() + " Proxy Details",
+            user = self.configuration.proxies[protocol][1],
+            passwd = self.configuration.proxies[protocol][2],
+            parent = parent,
+            flags = gtk.DIALOG_MODAL
+                    | gtk.DIALOG_DESTROY_WITH_PARENT
+                    | gtk.DIALOG_NO_SEPARATOR)
+        dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_OK)
+        response = dialog.run()
+        if response == gtk.RESPONSE_OK:
+            self.configuration.proxies[protocol][1] = dialog.user
+            self.configuration.proxies[protocol][2] = dialog.passwd
+            self.refresh_proxy_components()
+        dialog.destroy()    
+
+    def rootfs_combo_changed_cb(self, rootfs_combo, all_package_format, check_hbox):
+        combo_item = self.rootfs_combo.get_active_text()
+        for child in check_hbox.get_children():
+            if isinstance(child, gtk.CheckButton):
+                check_hbox.remove(child)
+        for format in all_package_format:
+            if format != combo_item:
+                check_button = gtk.CheckButton(format)
+                check_hbox.pack_start(check_button, expand=False, fill=False)
+        check_hbox.show_all()
+
+    def gen_pkgfmt_widget(self, curr_package_format, all_package_format, tooltip_combo="", tooltip_extra=""):
+        pkgfmt_hbox = gtk.HBox(False, 24)
+
+        rootfs_vbox = gtk.VBox(False, 6)
+        pkgfmt_hbox.pack_start(rootfs_vbox, expand=False, fill=False)
+
+        label = self.gen_label_widget("Root file system package format")
+        rootfs_vbox.pack_start(label, expand=False, fill=False)
+
+        rootfs_format = ""
+        if curr_package_format:
+            rootfs_format = curr_package_format.split()[0]
+
+        rootfs_format_widget, rootfs_combo = self.gen_combo_widget(rootfs_format, all_package_format, tooltip_combo)
+        rootfs_vbox.pack_start(rootfs_format_widget, expand=False, fill=False)
+
+        extra_vbox = gtk.VBox(False, 6)
+        pkgfmt_hbox.pack_start(extra_vbox, expand=False, fill=False)
+
+        label = self.gen_label_widget("Additional package formats")
+        extra_vbox.pack_start(label, expand=False, fill=False)
+
+        check_hbox = gtk.HBox(False, 12)
+        extra_vbox.pack_start(check_hbox, expand=False, fill=False)
+        for format in all_package_format:
+            if format != rootfs_format:
+                check_button = gtk.CheckButton(format)
+                is_active = (format in curr_package_format.split())
+                check_button.set_active(is_active)
+                check_hbox.pack_start(check_button, expand=False, fill=False)
+
+        info = HobInfoButton(tooltip_extra, self)
+        check_hbox.pack_end(info, expand=False, fill=False)
+
+        rootfs_combo.connect("changed", self.rootfs_combo_changed_cb, all_package_format, check_hbox)
+
+        pkgfmt_hbox.show_all()
+
+        return pkgfmt_hbox, rootfs_combo, check_hbox
+
+    def editable_settings_cell_edited(self, cell, path_string, new_text, model):
+        it = model.get_iter_from_string(path_string)
+        column = cell.get_data("column")
+        model.set(it, column, new_text)
+
+    def editable_settings_add_item_clicked(self, button, model):
+        new_item = ["##KEY##", "##VALUE##"]
+
+        iter = model.append()
+        model.set (iter,
+            0, new_item[0],
+            1, new_item[1],
+       )
+
+    def editable_settings_remove_item_clicked(self, button, treeview):
+        selection = treeview.get_selection()
+        model, iter = selection.get_selected()
+
+        if iter:
+            path = model.get_path(iter)[0]
+            model.remove(iter)
+ 
+    def gen_editable_settings(self, setting, tooltip=""):
+        setting_hbox = gtk.HBox(False, 12)
+
+        vbox = gtk.VBox(False, 12)
+        setting_hbox.pack_start(vbox, expand=True, fill=True)
+
+        setting_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
+        for key in setting.keys():
+            setting_store.set(setting_store.append(), 0, key, 1, setting[key])
+
+        setting_tree = gtk.TreeView(setting_store)
+        setting_tree.set_headers_visible(True)
+        setting_tree.set_size_request(300, 100)
+
+        col = gtk.TreeViewColumn('Key')
+        col.set_min_width(100)
+        col.set_max_width(150)
+        col.set_resizable(True)
+        col1 = gtk.TreeViewColumn('Value')
+        col1.set_min_width(100)
+        col1.set_max_width(150)
+        col1.set_resizable(True)
+        setting_tree.append_column(col)
+        setting_tree.append_column(col1)
+        cell = gtk.CellRendererText()
+        cell.set_property('width-chars', 10)
+        cell.set_property('editable', True)
+        cell.set_data("column", 0)
+        cell.connect("edited", self.editable_settings_cell_edited, setting_store)
+        cell1 = gtk.CellRendererText()
+        cell1.set_property('width-chars', 10)
+        cell1.set_property('editable', True)
+        cell1.set_data("column", 1)
+        cell1.connect("edited", self.editable_settings_cell_edited, setting_store)
+        col.pack_start(cell, True)
+        col1.pack_end(cell1, True)
+        col.set_attributes(cell, text=0)
+        col1.set_attributes(cell1, text=1)
+
+        scroll = gtk.ScrolledWindow()
+        scroll.set_shadow_type(gtk.SHADOW_IN)
+        scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+        scroll.add(setting_tree)
+        vbox.pack_start(scroll, expand=True, fill=True)
+
+        # some buttons
+        hbox = gtk.HBox(True, 6)
+        vbox.pack_start(hbox, False, False)
+
+        button = gtk.Button(stock=gtk.STOCK_ADD)
+        button.connect("clicked", self.editable_settings_add_item_clicked, setting_store)
+        hbox.pack_start(button)
+
+        button = gtk.Button(stock=gtk.STOCK_REMOVE)
+        button.connect("clicked", self.editable_settings_remove_item_clicked, setting_tree)
+        hbox.pack_start(button)
+
+        info = HobInfoButton(tooltip, self)
+        setting_hbox.pack_start(info, expand=False, fill=False)
+
+        return setting_hbox, setting_store
+
+    def create_others_page(self):
+        advanced_vbox = gtk.VBox(False, 6)
+        advanced_vbox.set_border_width(6)
+
+        sub_vbox = gtk.VBox(False, 6)
+        advanced_vbox.pack_start(sub_vbox, expand=True, fill=True)
+        label = self.gen_label_widget("<span weight=\"bold\">Add your own variables:</span>")
+        tooltip = "These are key/value pairs for your extra settings. Click \'Add\' and then directly edit the key and the value"
+        setting_widget, self.setting_store = self.gen_editable_settings(self.configuration.extra_setting,"<b>Add your own variables</b>" + "*" + tooltip)
+        sub_vbox.pack_start(label, expand=False, fill=False)
+        sub_vbox.pack_start(setting_widget, expand=True, fill=True)
+
+        return advanced_vbox
+
+    def create_visual_elements(self):
+        self.nb = gtk.Notebook()
+        self.nb.set_show_tabs(True)        
+        self.nb.append_page(self.create_build_environment_page(), gtk.Label("Build environment"))
+        self.nb.append_page(self.create_shared_state_page(), gtk.Label("Shared state"))
+        self.nb.append_page(self.create_network_page(), gtk.Label("Network"))        
+        self.nb.append_page(self.create_others_page(), gtk.Label("Others"))
+        self.nb.set_current_page(0)
+        self.vbox.pack_start(self.nb, expand=True, fill=True)
+        self.vbox.pack_end(gtk.HSeparator(), expand=True, fill=True)
+
+        self.show_all()
+
+    def destroy(self):
+        self.handler.disconnect(self.proxy_test_passed_id)
+        self.handler.disconnect(self.proxy_test_failed_id)
+        super(SimpleSettingsDialog, self).destroy()
diff --git a/bitbake/lib/bb/ui/crumbs/hobcolor.py b/bitbake/lib/bb/ui/crumbs/hobcolor.py
new file mode 100644
index 0000000..3316542
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hobcolor.py
@@ -0,0 +1,38 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2012        Intel Corporation
+#
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+class HobColors:
+    WHITE        = "#ffffff"
+    PALE_GREEN   = "#aaffaa"
+    ORANGE       = "#eb8e68"
+    PALE_RED     = "#ffaaaa"
+    GRAY         = "#aaaaaa"
+    LIGHT_GRAY   = "#dddddd"
+    SLIGHT_DARK  = "#5f5f5f"
+    DARK         = "#3c3b37"
+    BLACK        = "#000000"
+    PALE_BLUE    = "#53b8ff"
+    DEEP_RED     = "#aa3e3e"
+    KHAKI        = "#fff68f"
+
+    OK = WHITE
+    RUNNING = PALE_GREEN
+    WARNING = ORANGE
+    ERROR = PALE_RED
diff --git a/bitbake/lib/bb/ui/crumbs/hobeventhandler.py b/bitbake/lib/bb/ui/crumbs/hobeventhandler.py
new file mode 100644
index 0000000..b71fb33
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hobeventhandler.py
@@ -0,0 +1,645 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011        Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gobject
+import logging
+import ast
+from bb.ui.crumbs.runningbuild import RunningBuild
+
+class HobHandler(gobject.GObject):
+
+    """
+    This object does BitBake event handling for the hob gui.
+    """
+    __gsignals__ = {
+         "package-formats-updated" : (gobject.SIGNAL_RUN_LAST,
+                                      gobject.TYPE_NONE,
+                                     (gobject.TYPE_PYOBJECT,)),
+         "config-updated"          : (gobject.SIGNAL_RUN_LAST,
+                                      gobject.TYPE_NONE,
+                                     (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT,)),
+         "command-succeeded"       : (gobject.SIGNAL_RUN_LAST,
+                                      gobject.TYPE_NONE,
+                                     (gobject.TYPE_INT,)),
+         "command-failed"          : (gobject.SIGNAL_RUN_LAST,
+                                      gobject.TYPE_NONE,
+                                     (gobject.TYPE_STRING,)),
+         "parsing-warning"         : (gobject.SIGNAL_RUN_LAST,
+                                      gobject.TYPE_NONE,
+                                     (gobject.TYPE_STRING,)),
+         "sanity-failed"           : (gobject.SIGNAL_RUN_LAST,
+                                      gobject.TYPE_NONE,
+                                     (gobject.TYPE_STRING, gobject.TYPE_INT)),
+         "generating-data"         : (gobject.SIGNAL_RUN_LAST,
+                                      gobject.TYPE_NONE,
+                                     ()),
+         "data-generated"          : (gobject.SIGNAL_RUN_LAST,
+                                      gobject.TYPE_NONE,
+                                     ()),
+         "parsing-started"         : (gobject.SIGNAL_RUN_LAST,
+                                      gobject.TYPE_NONE,
+                                     (gobject.TYPE_PYOBJECT,)),
+         "parsing"                 : (gobject.SIGNAL_RUN_LAST,
+                                      gobject.TYPE_NONE,
+                                     (gobject.TYPE_PYOBJECT,)),
+         "parsing-completed"       : (gobject.SIGNAL_RUN_LAST,
+                                      gobject.TYPE_NONE,
+                                     (gobject.TYPE_PYOBJECT,)),
+         "recipe-populated"        : (gobject.SIGNAL_RUN_LAST,
+                                      gobject.TYPE_NONE,
+                                     ()),
+         "package-populated"       : (gobject.SIGNAL_RUN_LAST,
+                                      gobject.TYPE_NONE,
+                                     ()),
+         "network-passed"          : (gobject.SIGNAL_RUN_LAST,
+                                      gobject.TYPE_NONE,
+                                     ()),
+         "network-failed"          : (gobject.SIGNAL_RUN_LAST,
+                                      gobject.TYPE_NONE,
+                                     ()),
+    }
+
+    (GENERATE_CONFIGURATION, GENERATE_RECIPES, GENERATE_PACKAGES, GENERATE_IMAGE, POPULATE_PACKAGEINFO, SANITY_CHECK, NETWORK_TEST) = range(7)
+    (SUB_PATH_LAYERS, SUB_FILES_DISTRO, SUB_FILES_MACH, SUB_FILES_SDKMACH, SUB_MATCH_CLASS, SUB_PARSE_CONFIG, SUB_SANITY_CHECK,
+     SUB_GNERATE_TGTS, SUB_GENERATE_PKGINFO, SUB_BUILD_RECIPES, SUB_BUILD_IMAGE, SUB_NETWORK_TEST) = range(12)
+
+    def __init__(self, server, recipe_model, package_model):
+        super(HobHandler, self).__init__()
+
+        self.build = RunningBuild(sequential=True)
+
+        self.recipe_model = recipe_model
+        self.package_model = package_model
+
+        self.commands_async = []
+        self.generating = False
+        self.current_phase = None
+        self.building = False
+        self.recipe_queue = []
+        self.package_queue = []
+
+        self.server = server
+        self.error_msg = ""
+        self.initcmd = None
+        self.parsing = False
+
+    def set_busy(self):
+        if not self.generating:
+            self.emit("generating-data")
+            self.generating = True
+
+    def clear_busy(self):
+        if self.generating:
+            self.emit("data-generated")
+            self.generating = False
+
+    def runCommand(self, commandline):
+        try:
+            result, error = self.server.runCommand(commandline)
+            if error:
+                raise Exception("Error running command '%s': %s" % (commandline, error))
+            return result
+        except Exception as e:
+            self.commands_async = []
+            self.clear_busy()
+            self.emit("command-failed", "Hob Exception - %s" % (str(e)))
+            return None
+
+    def run_next_command(self, initcmd=None):
+        if initcmd != None:
+            self.initcmd = initcmd
+
+        if self.commands_async:
+            self.set_busy()
+            next_command = self.commands_async.pop(0)
+        else:
+            self.clear_busy()
+            if self.initcmd != None:
+                self.emit("command-succeeded", self.initcmd)
+            return
+
+        if next_command == self.SUB_PATH_LAYERS:
+            self.runCommand(["findConfigFilePath", "bblayers.conf"])
+        elif next_command == self.SUB_FILES_DISTRO:
+            self.runCommand(["findConfigFiles", "DISTRO"])
+        elif next_command == self.SUB_FILES_MACH:
+            self.runCommand(["findConfigFiles", "MACHINE"])
+        elif next_command == self.SUB_FILES_SDKMACH:
+            self.runCommand(["findConfigFiles", "MACHINE-SDK"])
+        elif next_command == self.SUB_MATCH_CLASS:
+            self.runCommand(["findFilesMatchingInDir", "rootfs_", "classes"])
+        elif next_command == self.SUB_PARSE_CONFIG:
+            self.runCommand(["resetCooker"])
+        elif next_command == self.SUB_GNERATE_TGTS:
+            self.runCommand(["generateTargetsTree", "classes/image.bbclass", []])
+        elif next_command == self.SUB_GENERATE_PKGINFO:
+            self.runCommand(["triggerEvent", "bb.event.RequestPackageInfo()"])
+        elif next_command == self.SUB_SANITY_CHECK:
+            self.runCommand(["triggerEvent", "bb.event.SanityCheck()"])
+        elif next_command == self.SUB_NETWORK_TEST:
+            self.runCommand(["triggerEvent", "bb.event.NetworkTest()"])
+        elif next_command == self.SUB_BUILD_RECIPES:
+            self.clear_busy()
+            self.building = True
+            self.runCommand(["buildTargets", self.recipe_queue, self.default_task])
+            self.recipe_queue = []
+        elif next_command == self.SUB_BUILD_IMAGE:
+            self.clear_busy()
+            self.building = True
+            target = self.image
+
+            if self.base_image:
+                # Request the build of a custom image
+                self.generate_hob_base_image(target)
+                self.set_var_in_file("LINGUAS_INSTALL", "", "local.conf")
+                hobImage = self.runCommand(["matchFile", target + ".bb"])
+                if self.base_image != self.recipe_model.__custom_image__:
+                    baseImage = self.runCommand(["matchFile", self.base_image + ".bb"])
+                    version = self.runCommand(["generateNewImage", hobImage, baseImage, self.package_queue, True, ""])
+                    target += version
+                    self.recipe_model.set_custom_image_version(version)
+
+            targets = [target]
+            if self.toolchain_packages:
+                self.set_var_in_file("TOOLCHAIN_TARGET_TASK", " ".join(self.toolchain_packages), "local.conf")
+                targets.append(target + ":do_populate_sdk")
+
+            self.runCommand(["buildTargets", targets, self.default_task])
+
+    def display_error(self):
+        self.clear_busy()
+        self.emit("command-failed", self.error_msg)
+        self.error_msg = ""
+        if self.building:
+            self.building = False
+
+    def handle_event(self, event):
+        if not event:
+            return
+        if self.building:
+            self.current_phase = "building"
+            self.build.handle_event(event)
+
+        if isinstance(event, bb.event.PackageInfo):
+            self.package_model.populate(event._pkginfolist)
+            self.emit("package-populated")
+            self.run_next_command()
+
+        elif isinstance(event, bb.event.SanityCheckPassed):
+            reparse = self.runCommand(["getVariable", "BB_INVALIDCONF"]) or None
+            if reparse is True:
+                self.set_var_in_file("BB_INVALIDCONF", False, "local.conf")
+                self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""])
+                self.commands_async.prepend(self.SUB_PARSE_CONFIG)
+            self.run_next_command()
+
+        elif isinstance(event, bb.event.SanityCheckFailed):
+            self.emit("sanity-failed", event._msg, event._network_error)
+
+        elif isinstance(event, logging.LogRecord):
+            if not self.building:
+                if event.levelno >= logging.ERROR:
+                    formatter = bb.msg.BBLogFormatter()
+                    msg = formatter.format(event)
+                    self.error_msg += msg + '\n'
+                elif event.levelno >= logging.WARNING and self.parsing == True:
+                    formatter = bb.msg.BBLogFormatter()
+                    msg = formatter.format(event)
+                    warn_msg = msg + '\n'
+                    self.emit("parsing-warning", warn_msg)
+
+        elif isinstance(event, bb.event.TargetsTreeGenerated):
+            self.current_phase = "data generation"
+            if event._model:
+                self.recipe_model.populate(event._model)
+                self.emit("recipe-populated")
+        elif isinstance(event, bb.event.ConfigFilesFound):
+            self.current_phase = "configuration lookup"
+            var = event._variable
+            values = event._values
+            values.sort()
+            self.emit("config-updated", var, values)
+        elif isinstance(event, bb.event.ConfigFilePathFound):
+            self.current_phase = "configuration lookup"
+        elif isinstance(event, bb.event.FilesMatchingFound):
+            self.current_phase = "configuration lookup"
+            # FIXME: hard coding, should at least be a variable shared between
+            # here and the caller
+            if event._pattern == "rootfs_":
+                formats = []
+                for match in event._matches:
+                    classname, sep, cls = match.rpartition(".")
+                    fs, sep, format = classname.rpartition("_")
+                    formats.append(format)
+                formats.sort()
+                self.emit("package-formats-updated", formats)
+        elif isinstance(event, bb.command.CommandCompleted):
+            self.current_phase = None
+            self.run_next_command()
+        elif isinstance(event, bb.command.CommandFailed):
+            if event.error not in ("Forced shutdown", "Stopped build"):
+                self.error_msg += event.error
+            self.commands_async = []
+            self.display_error()
+        elif isinstance(event, (bb.event.ParseStarted,
+                 bb.event.CacheLoadStarted,
+                 bb.event.TreeDataPreparationStarted,
+                 )):
+            message = {}
+            message["eventname"] = bb.event.getName(event)
+            message["current"] = 0
+            message["total"] = None
+            message["title"] = "Parsing recipes"
+            self.emit("parsing-started", message)
+            if isinstance(event, bb.event.ParseStarted):
+                self.parsing = True
+        elif isinstance(event, (bb.event.ParseProgress,
+                bb.event.CacheLoadProgress,
+                bb.event.TreeDataPreparationProgress)):
+            message = {}
+            message["eventname"] = bb.event.getName(event)
+            message["current"] = event.current
+            message["total"] = event.total
+            message["title"] = "Parsing recipes"
+            self.emit("parsing", message)
+        elif isinstance(event, (bb.event.ParseCompleted,
+                bb.event.CacheLoadCompleted,
+                bb.event.TreeDataPreparationCompleted)):
+            message = {}
+            message["eventname"] = bb.event.getName(event)
+            message["current"] = event.total
+            message["total"] = event.total
+            message["title"] = "Parsing recipes"
+            self.emit("parsing-completed", message)
+            if isinstance(event, bb.event.ParseCompleted):
+                self.parsing = False
+        elif isinstance(event, bb.event.NetworkTestFailed):
+            self.emit("network-failed")
+            self.run_next_command()
+        elif isinstance(event, bb.event.NetworkTestPassed):
+            self.emit("network-passed")
+            self.run_next_command()
+
+        if self.error_msg and not self.commands_async:
+            self.display_error()
+
+        return
+
+    def init_cooker(self):
+        self.runCommand(["createConfigFile", ".hob.conf"])
+
+    def set_extra_inherit(self, bbclass):
+        self.append_var_in_file("INHERIT", bbclass, ".hob.conf")
+
+    def set_bblayers(self, bblayers):
+        self.set_var_in_file("BBLAYERS", " ".join(bblayers), "bblayers.conf")
+
+    def set_machine(self, machine):
+        if machine:
+            self.early_assign_var_in_file("MACHINE", machine, "local.conf")
+
+    def set_sdk_machine(self, sdk_machine):
+        self.set_var_in_file("SDKMACHINE", sdk_machine, "local.conf")
+
+    def set_image_fstypes(self, image_fstypes):
+        self.set_var_in_file("IMAGE_FSTYPES", image_fstypes, "local.conf")
+
+    def set_distro(self, distro):
+        self.set_var_in_file("DISTRO", distro, "local.conf")
+
+    def set_package_format(self, format):
+        package_classes = ""
+        for pkgfmt in format.split():
+            package_classes += ("package_%s" % pkgfmt + " ")
+        self.set_var_in_file("PACKAGE_CLASSES", package_classes, "local.conf")
+
+    def set_bbthreads(self, threads):
+        self.set_var_in_file("BB_NUMBER_THREADS", threads, "local.conf")
+
+    def set_pmake(self, threads):
+        pmake = "-j %s" % threads
+        self.set_var_in_file("PARALLEL_MAKE", pmake, "local.conf")
+
+    def set_dl_dir(self, directory):
+        self.set_var_in_file("DL_DIR", directory, "local.conf")
+
+    def set_sstate_dir(self, directory):
+        self.set_var_in_file("SSTATE_DIR", directory, "local.conf")
+
+    def set_sstate_mirrors(self, url):
+        self.set_var_in_file("SSTATE_MIRRORS", url, "local.conf")
+
+    def set_extra_size(self, image_extra_size):
+        self.set_var_in_file("IMAGE_ROOTFS_EXTRA_SPACE", str(image_extra_size), "local.conf")
+
+    def set_rootfs_size(self, image_rootfs_size):
+        self.set_var_in_file("IMAGE_ROOTFS_SIZE", str(image_rootfs_size), "local.conf")
+
+    def set_incompatible_license(self, incompat_license):
+        self.set_var_in_file("INCOMPATIBLE_LICENSE", incompat_license, "local.conf")
+
+    def set_extra_setting(self, extra_setting):
+        self.set_var_in_file("EXTRA_SETTING", extra_setting, "local.conf")
+
+    def set_extra_config(self, extra_setting):
+        old_extra_setting = self.runCommand(["getVariable", "EXTRA_SETTING"]) or {}
+        old_extra_setting = str(old_extra_setting)
+
+        old_extra_setting = ast.literal_eval(old_extra_setting)
+        if not type(old_extra_setting) == dict:
+            old_extra_setting = {}
+
+        # settings not changed
+        if old_extra_setting == extra_setting:
+            return
+
+        # remove the old EXTRA SETTING variable
+        self.remove_var_from_file("EXTRA_SETTING")
+
+        # remove old settings from conf
+        for key in old_extra_setting.keys():
+            if key not in extra_setting:
+                self.remove_var_from_file(key)
+
+        # add new settings
+        for key, value in extra_setting.iteritems():
+            self.set_var_in_file(key, value, "local.conf")
+
+        if extra_setting:
+            self.set_var_in_file("EXTRA_SETTING", extra_setting, "local.conf")
+
+    def set_http_proxy(self, http_proxy):
+        self.set_var_in_file("http_proxy", http_proxy, "local.conf")
+
+    def set_https_proxy(self, https_proxy):
+        self.set_var_in_file("https_proxy", https_proxy, "local.conf")
+
+    def set_ftp_proxy(self, ftp_proxy):
+        self.set_var_in_file("ftp_proxy", ftp_proxy, "local.conf")
+
+    def set_socks_proxy(self, socks_proxy):
+        self.set_var_in_file("all_proxy", socks_proxy, "local.conf")
+
+    def set_cvs_proxy(self, host, port):
+        self.set_var_in_file("CVS_PROXY_HOST", host, "local.conf")
+        self.set_var_in_file("CVS_PROXY_PORT", port, "local.conf")
+
+    def request_package_info(self):
+        self.commands_async.append(self.SUB_GENERATE_PKGINFO)
+        self.run_next_command(self.POPULATE_PACKAGEINFO)
+
+    def trigger_sanity_check(self):
+        self.commands_async.append(self.SUB_SANITY_CHECK)
+        self.run_next_command(self.SANITY_CHECK)
+
+    def trigger_network_test(self):
+        self.commands_async.append(self.SUB_NETWORK_TEST)
+        self.run_next_command(self.NETWORK_TEST)
+
+    def generate_configuration(self):
+        self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""])
+        self.commands_async.append(self.SUB_PARSE_CONFIG)
+        self.commands_async.append(self.SUB_PATH_LAYERS)
+        self.commands_async.append(self.SUB_FILES_DISTRO)
+        self.commands_async.append(self.SUB_FILES_MACH)
+        self.commands_async.append(self.SUB_FILES_SDKMACH)
+        self.commands_async.append(self.SUB_MATCH_CLASS)
+        self.run_next_command(self.GENERATE_CONFIGURATION)
+
+    def generate_recipes(self):
+        self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""])
+        self.commands_async.append(self.SUB_PARSE_CONFIG)
+        self.commands_async.append(self.SUB_GNERATE_TGTS)
+        self.run_next_command(self.GENERATE_RECIPES)
+
+    def generate_packages(self, tgts, default_task="build"):
+        targets = []
+        targets.extend(tgts)
+        self.recipe_queue = targets
+        self.default_task = default_task
+        self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""])
+        self.commands_async.append(self.SUB_PARSE_CONFIG)
+        self.commands_async.append(self.SUB_BUILD_RECIPES)
+        self.run_next_command(self.GENERATE_PACKAGES)
+
+    def generate_image(self, image, base_image, image_packages=None, toolchain_packages=None, default_task="build"):
+        self.image = image
+        self.base_image = base_image
+        if image_packages:
+            self.package_queue = image_packages
+        else:
+            self.package_queue = []
+        if toolchain_packages:
+            self.toolchain_packages = toolchain_packages
+        else:
+            self.toolchain_packages = []
+        self.default_task = default_task
+        self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""])
+        self.commands_async.append(self.SUB_PARSE_CONFIG)
+        self.commands_async.append(self.SUB_BUILD_IMAGE)
+        self.run_next_command(self.GENERATE_IMAGE)
+
+    def generate_new_image(self, image, base_image, package_queue, description):
+        if base_image:
+            base_image = self.runCommand(["matchFile", self.base_image + ".bb"])
+        self.runCommand(["generateNewImage", image, base_image, package_queue, False, description])
+
+    def generate_hob_base_image(self, hob_image):
+        image_dir = self.get_topdir() + "/recipes/images/"
+        recipe_name = hob_image + ".bb"
+        self.ensure_dir(image_dir)
+        self.generate_new_image(image_dir + recipe_name, None, [], "")
+
+    def ensure_dir(self, directory):
+        self.runCommand(["ensureDir", directory])
+
+    def build_succeeded_async(self):
+        self.building = False
+
+    def build_failed_async(self):
+        self.initcmd = None
+        self.commands_async = []
+        self.building = False
+
+    def cancel_parse(self):
+        self.runCommand(["stateForceShutdown"])
+
+    def cancel_build(self, force=False):
+        if force:
+            # Force the cooker to stop as quickly as possible
+            self.runCommand(["stateForceShutdown"])
+        else:
+            # Wait for tasks to complete before shutting down, this helps
+            # leave the workdir in a usable state
+            self.runCommand(["stateShutdown"])
+
+    def reset_build(self):
+        self.build.reset()
+
+    def get_logfile(self):
+        return self.server.runCommand(["getVariable", "BB_CONSOLELOG"])[0]
+
+    def get_topdir(self):
+        return self.runCommand(["getVariable", "TOPDIR"]) or ""
+
+    def _remove_redundant(self, string):
+        ret = []
+        for i in string.split():
+            if i not in ret:
+                ret.append(i)
+        return " ".join(ret)
+
+    def set_var_in_file(self, var, val, default_file=None):
+        self.runCommand(["enableDataTracking"])
+        self.server.runCommand(["setVarFile", var, val, default_file, "set"])
+        self.runCommand(["disableDataTracking"])
+
+    def early_assign_var_in_file(self, var, val, default_file=None):
+        self.runCommand(["enableDataTracking"])
+        self.server.runCommand(["setVarFile", var, val, default_file, "earlyAssign"])
+        self.runCommand(["disableDataTracking"])
+
+    def remove_var_from_file(self, var):
+        self.server.runCommand(["removeVarFile", var])
+
+    def append_var_in_file(self, var, val, default_file=None):
+        self.server.runCommand(["setVarFile", var, val, default_file, "append"])
+
+    def append_to_bbfiles(self, val):
+        bbfiles = self.runCommand(["getVariable", "BBFILES", "False"]) or ""
+        bbfiles = bbfiles.split()
+        if val not in bbfiles:
+            self.append_var_in_file("BBFILES", val, "bblayers.conf")
+
+    def get_parameters(self):
+        # retrieve the parameters from bitbake
+        params = {}
+        params["core_base"] = self.runCommand(["getVariable", "COREBASE"]) or ""
+        params["layer"] = self.runCommand(["getVariable", "BBLAYERS"]) or ""
+        params["layers_non_removable"] = self.runCommand(["getVariable", "BBLAYERS_NON_REMOVABLE"]) or ""
+        params["dldir"] = self.runCommand(["getVariable", "DL_DIR"]) or ""
+        params["machine"] = self.runCommand(["getVariable", "MACHINE"]) or ""
+        params["distro"] = self.runCommand(["getVariable", "DISTRO"]) or "defaultsetup"
+        params["pclass"] = self.runCommand(["getVariable", "PACKAGE_CLASSES"]) or ""
+        params["sstatedir"] = self.runCommand(["getVariable", "SSTATE_DIR"]) or ""
+        params["sstatemirror"] = self.runCommand(["getVariable", "SSTATE_MIRRORS"]) or ""
+
+        num_threads = self.runCommand(["getCpuCount"])
+        if not num_threads:
+            num_threads = 1
+            max_threads = 65536
+        else:
+            try:
+                num_threads = int(num_threads)
+                max_threads = 16 * num_threads
+            except:
+                num_threads = 1
+                max_threads = 65536
+        params["max_threads"] = max_threads
+
+        bbthread = self.runCommand(["getVariable", "BB_NUMBER_THREADS"])
+        if not bbthread:
+            bbthread = num_threads
+        else:
+            try:
+                bbthread = int(bbthread)
+            except:
+                bbthread = num_threads
+        params["bbthread"] = bbthread
+
+        pmake = self.runCommand(["getVariable", "PARALLEL_MAKE"])
+        if not pmake:
+            pmake = num_threads
+        elif isinstance(pmake, int):
+            pass
+        else:
+            try:
+                pmake = int(pmake.lstrip("-j "))
+            except:
+                pmake = num_threads
+        params["pmake"] = "-j %s" % pmake
+
+        params["image_addr"] = self.runCommand(["getVariable", "DEPLOY_DIR_IMAGE"]) or ""
+
+        image_extra_size = self.runCommand(["getVariable", "IMAGE_ROOTFS_EXTRA_SPACE"])
+        if not image_extra_size:
+            image_extra_size = 0
+        else:
+            try:
+                image_extra_size = int(image_extra_size)
+            except:
+                image_extra_size = 0
+        params["image_extra_size"] = image_extra_size
+
+        image_rootfs_size = self.runCommand(["getVariable", "IMAGE_ROOTFS_SIZE"])
+        if not image_rootfs_size:
+            image_rootfs_size = 0
+        else:
+            try:
+                image_rootfs_size = int(image_rootfs_size)
+            except:
+                image_rootfs_size = 0
+        params["image_rootfs_size"] = image_rootfs_size
+
+        image_overhead_factor = self.runCommand(["getVariable", "IMAGE_OVERHEAD_FACTOR"])
+        if not image_overhead_factor:
+            image_overhead_factor = 1
+        else:
+            try:
+                image_overhead_factor = float(image_overhead_factor)
+            except:
+                image_overhead_factor = 1
+        params['image_overhead_factor'] = image_overhead_factor
+
+        params["incompat_license"] = self._remove_redundant(self.runCommand(["getVariable", "INCOMPATIBLE_LICENSE"]) or "")
+        params["sdk_machine"] = self.runCommand(["getVariable", "SDKMACHINE"]) or self.runCommand(["getVariable", "SDK_ARCH"]) or ""
+
+        params["image_fstypes"] = self._remove_redundant(self.runCommand(["getVariable", "IMAGE_FSTYPES"]) or "")
+
+        params["image_types"] = self._remove_redundant(self.runCommand(["getVariable", "IMAGE_TYPES"]) or "")
+
+        params["conf_version"] = self.runCommand(["getVariable", "CONF_VERSION"]) or ""
+        params["lconf_version"] = self.runCommand(["getVariable", "LCONF_VERSION"]) or ""
+
+        params["runnable_image_types"] = self._remove_redundant(self.runCommand(["getVariable", "RUNNABLE_IMAGE_TYPES"]) or "")
+        params["runnable_machine_patterns"] = self._remove_redundant(self.runCommand(["getVariable", "RUNNABLE_MACHINE_PATTERNS"]) or "")
+        params["deployable_image_types"] = self._remove_redundant(self.runCommand(["getVariable", "DEPLOYABLE_IMAGE_TYPES"]) or "")
+        params["kernel_image_type"] = self.runCommand(["getVariable", "KERNEL_IMAGETYPE"]) or ""
+        params["tmpdir"] = self.runCommand(["getVariable", "TMPDIR"]) or ""
+        params["distro_version"] = self.runCommand(["getVariable", "DISTRO_VERSION"]) or ""
+        params["target_os"] = self.runCommand(["getVariable", "TARGET_OS"]) or ""
+        params["target_arch"] = self.runCommand(["getVariable", "TARGET_ARCH"]) or ""
+        params["tune_pkgarch"] = self.runCommand(["getVariable", "TUNE_PKGARCH"])  or ""
+        params["bb_version"] = self.runCommand(["getVariable", "BB_MIN_VERSION"]) or ""
+
+        params["default_task"] = self.runCommand(["getVariable", "BB_DEFAULT_TASK"]) or "build"
+
+        params["socks_proxy"] = self.runCommand(["getVariable", "all_proxy"]) or ""
+        params["http_proxy"] = self.runCommand(["getVariable", "http_proxy"]) or ""
+        params["ftp_proxy"] = self.runCommand(["getVariable", "ftp_proxy"]) or ""
+        params["https_proxy"] = self.runCommand(["getVariable", "https_proxy"]) or ""
+
+        params["cvs_proxy_host"] = self.runCommand(["getVariable", "CVS_PROXY_HOST"]) or ""
+        params["cvs_proxy_port"] = self.runCommand(["getVariable", "CVS_PROXY_PORT"]) or ""
+
+        params["image_white_pattern"] = self.runCommand(["getVariable", "BBUI_IMAGE_WHITE_PATTERN"]) or ""
+        params["image_black_pattern"] = self.runCommand(["getVariable", "BBUI_IMAGE_BLACK_PATTERN"]) or ""
+        return params
diff --git a/bitbake/lib/bb/ui/crumbs/hoblistmodel.py b/bitbake/lib/bb/ui/crumbs/hoblistmodel.py
new file mode 100644
index 0000000..50df156
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hoblistmodel.py
@@ -0,0 +1,903 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011        Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import gobject
+from bb.ui.crumbs.hobpages import HobPage
+
+#
+# PackageListModel
+#
+class PackageListModel(gtk.ListStore):
+    """
+    This class defines an gtk.ListStore subclass which will convert the output
+    of the bb.event.TargetsTreeGenerated event into a gtk.ListStore whilst also
+    providing convenience functions to access gtk.TreeModel subclasses which
+    provide filtered views of the data.
+    """
+
+    (COL_NAME, COL_VER, COL_REV, COL_RNM, COL_SEC, COL_SUM, COL_RDEP, COL_RPROV, COL_SIZE, COL_RCP, COL_BINB, COL_INC, COL_FADE_INC, COL_FONT, COL_FLIST) = range(15)
+
+    __gsignals__ = {
+        "package-selection-changed" : (gobject.SIGNAL_RUN_LAST,
+                                gobject.TYPE_NONE,
+                                ()),
+    }
+
+    __toolchain_required_packages__ = ["packagegroup-core-standalone-sdk-target", "packagegroup-core-standalone-sdk-target-dbg"]
+
+    def __init__(self):
+        self.rprov_pkg = {}
+        gtk.ListStore.__init__ (self,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_BOOLEAN,
+                                gobject.TYPE_BOOLEAN,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING)
+        self.sort_column_id, self.sort_order = PackageListModel.COL_NAME, gtk.SORT_ASCENDING
+
+    """
+    Find the model path for the item_name
+    Returns the path in the model or None
+    """
+    def find_path_for_item(self, item_name):
+        pkg = item_name
+        if item_name not in self.pn_path.keys():
+            if item_name not in self.rprov_pkg.keys():
+                return None
+            pkg = self.rprov_pkg[item_name]
+            if pkg not in self.pn_path.keys():
+                return None
+
+        return self.pn_path[pkg]
+
+    def find_item_for_path(self, item_path):
+        return self[item_path][self.COL_NAME]
+
+    """
+    Helper function to determine whether an item is an item specified by filter
+    """
+    def tree_model_filter(self, model, it, filter):
+        name = model.get_value(it, self.COL_NAME)
+
+        for key in filter.keys():
+            if key == self.COL_NAME:
+                if filter[key] != 'Search packages by name':
+                    if name and filter[key] not in name:
+                        return False
+            else:
+                if model.get_value(it, key) not in filter[key]:
+                    return False
+        self.filtered_nb += 1
+        return True
+
+    """
+    Create, if required, and return a filtered gtk.TreeModelSort
+    containing only the items specified by filter
+    """
+    def tree_model(self, filter, excluded_items_ahead=False, included_items_ahead=False, search_data=None, initial=False):
+        model = self.filter_new()
+        self.filtered_nb = 0
+        model.set_visible_func(self.tree_model_filter, filter)
+
+        sort = gtk.TreeModelSort(model)
+        sort.connect ('sort-column-changed', self.sort_column_changed_cb)
+        if initial:
+            sort.set_sort_column_id(PackageListModel.COL_NAME, gtk.SORT_ASCENDING)
+            sort.set_default_sort_func(None)
+        elif excluded_items_ahead:
+            sort.set_default_sort_func(self.exclude_item_sort_func, search_data)
+        elif included_items_ahead:
+            sort.set_default_sort_func(self.include_item_sort_func, search_data)
+        else:
+            if search_data and search_data!='Search recipes by name' and search_data!='Search package groups by name':
+                sort.set_default_sort_func(self.sort_func, search_data)
+            else:
+                sort.set_sort_column_id(self.sort_column_id, self.sort_order)
+                sort.set_default_sort_func(None)
+
+        sort.set_sort_func(PackageListModel.COL_INC, self.sort_column, PackageListModel.COL_INC)
+        sort.set_sort_func(PackageListModel.COL_SIZE, self.sort_column, PackageListModel.COL_SIZE)
+        sort.set_sort_func(PackageListModel.COL_BINB, self.sort_binb_column)
+        sort.set_sort_func(PackageListModel.COL_RCP, self.sort_column, PackageListModel.COL_RCP)
+        return sort
+
+    def sort_column_changed_cb (self, data):
+        self.sort_column_id, self.sort_order = data.get_sort_column_id ()
+
+    def sort_column(self, model, row1, row2, col):
+        value1 = model.get_value(row1, col)
+        value2 = model.get_value(row2, col)
+        if col==PackageListModel.COL_SIZE:
+            value1 = HobPage._string_to_size(value1)
+            value2 = HobPage._string_to_size(value2)
+
+        cmp_res = cmp(value1, value2)
+        if cmp_res!=0:
+            if col==PackageListModel.COL_INC:
+                return -cmp_res
+            else:
+                return cmp_res
+        else:
+            name1 = model.get_value(row1, PackageListModel.COL_NAME)
+            name2 = model.get_value(row2, PackageListModel.COL_NAME)
+            return cmp(name1,name2)
+
+    def sort_binb_column(self, model, row1, row2):
+        value1 = model.get_value(row1, PackageListModel.COL_BINB)
+        value2 = model.get_value(row2, PackageListModel.COL_BINB)
+        value1_list = value1.split(', ')
+        value2_list = value2.split(', ')
+
+        value1 = value1_list[0]
+        value2 = value2_list[0]
+
+        cmp_res = cmp(value1, value2)
+        if cmp_res==0:
+            cmp_size = cmp(len(value1_list), len(value2_list))
+            if cmp_size==0:
+                name1 = model.get_value(row1, PackageListModel.COL_NAME)
+                name2 = model.get_value(row2, PackageListModel.COL_NAME)
+                return cmp(name1,name2)
+            else:
+                return cmp_size
+        else:
+            return cmp_res
+
+    def exclude_item_sort_func(self, model, iter1, iter2, user_data=None):
+        if user_data:
+            val1 = model.get_value(iter1, PackageListModel.COL_NAME)
+            val2 = model.get_value(iter2, PackageListModel.COL_NAME)
+            return self.cmp_vals(val1, val2, user_data)
+        else:
+            val1 = model.get_value(iter1, PackageListModel.COL_FADE_INC)
+            val2 = model.get_value(iter2, PackageListModel.COL_INC)
+            return ((val1 == True) and (val2 == False))
+
+    def include_item_sort_func(self, model, iter1, iter2, user_data=None):
+        if user_data:
+            val1 = model.get_value(iter1, PackageListModel.COL_NAME)
+            val2 = model.get_value(iter2, PackageListModel.COL_NAME)
+            return self.cmp_vals(val1, val2, user_data)
+        else:
+            val1 = model.get_value(iter1, PackageListModel.COL_INC)
+            val2 = model.get_value(iter2, PackageListModel.COL_INC)
+            return ((val1 == False) and (val2 == True))
+
+    def sort_func(self, model, iter1, iter2, user_data):
+        val1 = model.get_value(iter1, PackageListModel.COL_NAME)
+        val2 = model.get_value(iter2, PackageListModel.COL_NAME)
+        return self.cmp_vals(val1, val2, user_data)
+
+    def cmp_vals(self, val1, val2, user_data):
+        if val1 is None or val2 is None:
+            return 0
+        elif val1.startswith(user_data) and not val2.startswith(user_data):
+            return -1
+        elif not val1.startswith(user_data) and val2.startswith(user_data):
+            return 1
+        else:
+            return cmp(val1, val2)
+
+    def convert_vpath_to_path(self, view_model, view_path):
+        # view_model is the model sorted
+        # get the path of the model filtered
+        filtered_model_path = view_model.convert_path_to_child_path(view_path)
+        # get the model filtered
+        filtered_model = view_model.get_model()
+        # get the path of the original model
+        path = filtered_model.convert_path_to_child_path(filtered_model_path)
+        return path
+
+    def convert_path_to_vpath(self, view_model, path):
+        it = view_model.get_iter_first()
+        while it:
+            name = self.find_item_for_path(path)
+            view_name = view_model.get_value(it, PackageListModel.COL_NAME)
+            if view_name == name:
+                view_path = view_model.get_path(it)
+                return view_path
+            it = view_model.iter_next(it)
+        return None
+
+    """
+    The populate() function takes as input the data from a
+    bb.event.PackageInfo event and populates the package list.
+    """
+    def populate(self, pkginfolist):
+        # First clear the model, in case repopulating
+        self.clear()
+
+        def getpkgvalue(pkgdict, key, pkgname, defaultval = None):
+            value = pkgdict.get('%s_%s' % (key, pkgname), None)
+            if not value:
+                value = pkgdict.get(key, defaultval)
+            return value
+
+        for pkginfo in pkginfolist:
+            pn = pkginfo['PN']
+            pv = pkginfo['PV']
+            pr = pkginfo['PR']
+            pkg = pkginfo['PKG']
+            pkgv = getpkgvalue(pkginfo, 'PKGV', pkg)
+            pkgr = getpkgvalue(pkginfo, 'PKGR', pkg)
+            # PKGSIZE is artificial, will always be overridden with the package name if present
+            pkgsize = int(pkginfo.get('PKGSIZE_%s' % pkg, "0"))
+            # PKG_%s is the renamed version
+            pkg_rename = pkginfo.get('PKG_%s' % pkg, "")
+            # The rest may be overridden or not
+            section = getpkgvalue(pkginfo, 'SECTION', pkg, "")
+            summary = getpkgvalue(pkginfo, 'SUMMARY', pkg, "")
+            rdep = getpkgvalue(pkginfo, 'RDEPENDS', pkg, "")
+            rrec = getpkgvalue(pkginfo, 'RRECOMMENDS', pkg, "")
+            rprov = getpkgvalue(pkginfo, 'RPROVIDES', pkg, "")
+            files_list = getpkgvalue(pkginfo, 'FILES_INFO', pkg, "")
+            for i in rprov.split():
+                self.rprov_pkg[i] = pkg
+
+            recipe = pn + '-' + pv + '-' + pr
+
+            allow_empty = getpkgvalue(pkginfo, 'ALLOW_EMPTY', pkg, "")
+
+            if pkgsize == 0 and not allow_empty:
+                continue
+
+            size = HobPage._size_to_string(pkgsize)
+            self.set(self.append(), self.COL_NAME, pkg, self.COL_VER, pkgv,
+                     self.COL_REV, pkgr, self.COL_RNM, pkg_rename,
+                     self.COL_SEC, section, self.COL_SUM, summary,
+                     self.COL_RDEP, rdep + ' ' + rrec,
+                     self.COL_RPROV, rprov, self.COL_SIZE, size,
+                     self.COL_RCP, recipe, self.COL_BINB, "",
+                     self.COL_INC, False, self.COL_FONT, '10', self.COL_FLIST, files_list)
+
+        self.pn_path = {}
+        it = self.get_iter_first()
+        while it:
+            pn = self.get_value(it, self.COL_NAME)
+            path = self.get_path(it)
+            self.pn_path[pn] = path
+            it = self.iter_next(it)
+
+    """
+    Update the model, send out the notification.
+    """
+    def selection_change_notification(self):
+        self.emit("package-selection-changed")
+
+    """
+    Check whether the item at item_path is included or not
+    """
+    def path_included(self, item_path):
+        return self[item_path][self.COL_INC]
+
+    """
+    Add this item, and any of its dependencies, to the image contents
+    """
+    def include_item(self, item_path, binb=""):
+        if self.path_included(item_path):
+            return
+
+        item_name = self[item_path][self.COL_NAME]
+        item_deps = self[item_path][self.COL_RDEP]
+
+        self[item_path][self.COL_INC] = True
+
+        item_bin = self[item_path][self.COL_BINB].split(', ')
+        if binb and not binb in item_bin:
+            item_bin.append(binb)
+            self[item_path][self.COL_BINB] = ', '.join(item_bin).lstrip(', ')
+
+        if item_deps:
+            # Ensure all of the items deps are included and, where appropriate,
+            # add this item to their COL_BINB
+            for dep in item_deps.split(" "):
+                if dep.startswith('('):
+                    continue
+                # If the contents model doesn't already contain dep, add it
+                dep_path = self.find_path_for_item(dep)
+                if not dep_path:
+                    continue
+                dep_included = self.path_included(dep_path)
+
+                if dep_included and not dep in item_bin:
+                    # don't set the COL_BINB to this item if the target is an
+                    # item in our own COL_BINB
+                    dep_bin = self[dep_path][self.COL_BINB].split(', ')
+                    if not item_name in dep_bin:
+                        dep_bin.append(item_name)
+                        self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
+                elif not dep_included:
+                    self.include_item(dep_path, binb=item_name)
+
+    def exclude_item(self, item_path):
+        if not self.path_included(item_path):
+            return
+
+        self[item_path][self.COL_INC] = False
+
+        item_name = self[item_path][self.COL_NAME]
+        item_deps = self[item_path][self.COL_RDEP]
+        if item_deps:
+            for dep in item_deps.split(" "):
+                if dep.startswith('('):
+                    continue
+                dep_path = self.find_path_for_item(dep)
+                if not dep_path:
+                    continue
+                dep_bin = self[dep_path][self.COL_BINB].split(', ')
+                if item_name in dep_bin:
+                    dep_bin.remove(item_name)
+                    self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
+
+        item_bin = self[item_path][self.COL_BINB].split(', ')
+        if item_bin:
+            for binb in item_bin:
+                binb_path = self.find_path_for_item(binb)
+                if not binb_path:
+                    continue
+                self.exclude_item(binb_path)
+
+    """
+    Empty self.contents by setting the include of each entry to None
+    """
+    def reset(self):
+        it = self.get_iter_first()
+        while it:
+            self.set(it,
+                     self.COL_INC, False,
+                     self.COL_BINB, "")
+            it = self.iter_next(it)
+
+        self.selection_change_notification()
+
+    def get_selected_packages(self):
+        packagelist = []
+
+        it = self.get_iter_first()
+        while it:
+            if self.get_value(it, self.COL_INC):
+                name = self.get_value(it, self.COL_NAME)
+                packagelist.append(name)
+            it = self.iter_next(it)
+
+        return packagelist
+
+    def get_user_selected_packages(self):
+        packagelist = []
+
+        it = self.get_iter_first()
+        while it:
+            if self.get_value(it, self.COL_INC):
+                binb = self.get_value(it, self.COL_BINB)
+                if binb == "User Selected":
+                    name = self.get_value(it, self.COL_NAME)
+                    packagelist.append(name)
+            it = self.iter_next(it)
+
+        return packagelist
+
+    def get_selected_packages_toolchain(self):
+        packagelist = []
+
+        it = self.get_iter_first()
+        while it:
+            if self.get_value(it, self.COL_INC):
+                name = self.get_value(it, self.COL_NAME)
+                if name.endswith("-dev") or name.endswith("-dbg"):
+                    packagelist.append(name)
+            it = self.iter_next(it)
+
+        return list(set(packagelist + self.__toolchain_required_packages__));
+
+    """
+    Package model may be incomplete, therefore when calling the
+    set_selected_packages(), some packages will not be set included.
+    Return the un-set packages list.
+    """
+    def set_selected_packages(self, packagelist, user_selected=False):
+        left = []
+        binb = 'User Selected' if user_selected else ''
+        for pn in packagelist:
+            if pn in self.pn_path.keys():
+                path = self.pn_path[pn]
+                self.include_item(item_path=path, binb=binb)
+            else:
+                left.append(pn)
+
+        self.selection_change_notification()
+        return left
+
+    """
+    Return the selected package size, unit is B.
+    """
+    def get_packages_size(self):
+        packages_size = 0
+        it = self.get_iter_first()
+        while it:
+            if self.get_value(it, self.COL_INC):
+                str_size = self.get_value(it, self.COL_SIZE)
+                if not str_size:
+                    continue
+
+                packages_size += HobPage._string_to_size(str_size)
+
+            it = self.iter_next(it)
+        return packages_size
+
+    """
+    Resync the state of included items to a backup column before performing the fadeout visible effect
+    """
+    def resync_fadeout_column(self, model_first_iter=None):
+        it = model_first_iter
+        while it:
+            active = self.get_value(it, self.COL_INC)
+            self.set(it, self.COL_FADE_INC, active)
+            it = self.iter_next(it)
+
+#
+# RecipeListModel
+#
+class RecipeListModel(gtk.ListStore):
+    """
+    This class defines an gtk.ListStore subclass which will convert the output
+    of the bb.event.TargetsTreeGenerated event into a gtk.ListStore whilst also
+    providing convenience functions to access gtk.TreeModel subclasses which
+    provide filtered views of the data.
+    """
+    (COL_NAME, COL_DESC, COL_LIC, COL_GROUP, COL_DEPS, COL_BINB, COL_TYPE, COL_INC, COL_IMG, COL_INSTALL, COL_PN, COL_FADE_INC, COL_SUMMARY, COL_VERSION,
+     COL_REVISION, COL_HOMEPAGE, COL_BUGTRACKER, COL_FILE) = range(18)
+
+    __custom_image__ = "Start with an empty image recipe"
+
+    __gsignals__ = {
+        "recipe-selection-changed" : (gobject.SIGNAL_RUN_LAST,
+                                  gobject.TYPE_NONE,
+                                 ()),
+        }
+
+    """
+    """
+    def __init__(self):
+        gtk.ListStore.__init__ (self,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_BOOLEAN,
+                                gobject.TYPE_BOOLEAN,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_BOOLEAN,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING)
+        self.sort_column_id, self.sort_order = RecipeListModel.COL_NAME, gtk.SORT_ASCENDING
+
+    """
+    Find the model path for the item_name
+    Returns the path in the model or None
+    """
+    def find_path_for_item(self, item_name):
+        if self.non_target_name(item_name) or item_name not in self.pn_path.keys():
+            return None
+        else:
+            return self.pn_path[item_name]
+
+    def find_item_for_path(self, item_path):
+        return self[item_path][self.COL_NAME]
+
+    """
+    Helper method to determine whether name is a target pn
+    """
+    def non_target_name(self, name):
+        if name and ('-native' in name):
+            return True
+        return False
+
+    """
+    Helper function to determine whether an item is an item specified by filter
+    """
+    def tree_model_filter(self, model, it, filter):
+        name = model.get_value(it, self.COL_NAME)
+        if self.non_target_name(name):
+            return False
+
+        for key in filter.keys():
+            if key == self.COL_NAME:
+                if filter[key] != 'Search recipes by name' and filter[key] != 'Search package groups by name':
+                    if filter[key] not in name:
+                        return False
+            else:
+                if model.get_value(it, key) not in filter[key]:
+                    return False
+        self.filtered_nb += 1
+
+        return True
+
+    def exclude_item_sort_func(self, model, iter1, iter2, user_data=None):
+        if user_data:
+            val1 = model.get_value(iter1, RecipeListModel.COL_NAME)
+            val2 = model.get_value(iter2, RecipeListModel.COL_NAME)
+            return self.cmp_vals(val1, val2, user_data)
+        else:
+            val1 = model.get_value(iter1, RecipeListModel.COL_FADE_INC)
+            val2 = model.get_value(iter2, RecipeListModel.COL_INC)
+            return ((val1 == True) and (val2 == False))
+
+    def include_item_sort_func(self, model, iter1, iter2, user_data=None):
+        if user_data:
+            val1 = model.get_value(iter1, RecipeListModel.COL_NAME)
+            val2 = model.get_value(iter2, RecipeListModel.COL_NAME)
+            return self.cmp_vals(val1, val2, user_data)
+        else:
+            val1 = model.get_value(iter1, RecipeListModel.COL_INC)
+            val2 = model.get_value(iter2, RecipeListModel.COL_INC)
+            return ((val1 == False) and (val2 == True))
+
+    def sort_func(self, model, iter1, iter2, user_data):
+        val1 = model.get_value(iter1, RecipeListModel.COL_NAME)
+        val2 = model.get_value(iter2, RecipeListModel.COL_NAME)
+        return self.cmp_vals(val1, val2, user_data)
+
+    def cmp_vals(self, val1, val2, user_data):
+        if val1 is None or val2 is None:
+            return 0
+        elif val1.startswith(user_data) and not val2.startswith(user_data):
+            return -1
+        elif not val1.startswith(user_data) and val2.startswith(user_data):
+            return 1
+        else:
+            return cmp(val1, val2)
+
+    """
+    Create, if required, and return a filtered gtk.TreeModelSort
+    containing only the items specified by filter
+    """
+    def tree_model(self, filter, excluded_items_ahead=False, included_items_ahead=False, search_data=None, initial=False):
+        model = self.filter_new()
+        self.filtered_nb = 0
+        model.set_visible_func(self.tree_model_filter, filter)
+
+        sort = gtk.TreeModelSort(model)
+        sort.connect ('sort-column-changed', self.sort_column_changed_cb)
+        if initial:
+            sort.set_sort_column_id(RecipeListModel.COL_NAME, gtk.SORT_ASCENDING)
+            sort.set_default_sort_func(None)
+        elif excluded_items_ahead:
+            sort.set_default_sort_func(self.exclude_item_sort_func, search_data)
+        elif included_items_ahead:
+            sort.set_default_sort_func(self.include_item_sort_func, search_data)
+        else:
+            if search_data and search_data!='Search recipes by name' and search_data!='Search package groups by name':
+                sort.set_default_sort_func(self.sort_func, search_data)
+            else:
+                sort.set_sort_column_id(self.sort_column_id, self.sort_order)
+                sort.set_default_sort_func(None)
+
+        sort.set_sort_func(RecipeListModel.COL_INC, self.sort_column, RecipeListModel.COL_INC)
+        sort.set_sort_func(RecipeListModel.COL_GROUP, self.sort_column, RecipeListModel.COL_GROUP)
+        sort.set_sort_func(RecipeListModel.COL_BINB, self.sort_binb_column)
+        sort.set_sort_func(RecipeListModel.COL_LIC, self.sort_column, RecipeListModel.COL_LIC)
+        return sort
+
+    def sort_column_changed_cb (self, data):
+        self.sort_column_id, self.sort_order = data.get_sort_column_id ()
+
+    def sort_column(self, model, row1, row2, col):
+        value1 = model.get_value(row1, col)
+        value2 = model.get_value(row2, col)
+        cmp_res = cmp(value1, value2)
+        if cmp_res!=0:
+            if col==RecipeListModel.COL_INC:
+                return -cmp_res
+            else:
+                return cmp_res
+        else:
+            name1 = model.get_value(row1, RecipeListModel.COL_NAME)
+            name2 = model.get_value(row2, RecipeListModel.COL_NAME)
+            return cmp(name1,name2)
+
+    def sort_binb_column(self, model, row1, row2):
+        value1 = model.get_value(row1, RecipeListModel.COL_BINB)
+        value2 = model.get_value(row2, RecipeListModel.COL_BINB)
+        value1_list = value1.split(', ')
+        value2_list = value2.split(', ')
+
+        value1 = value1_list[0]
+        value2 = value2_list[0]
+
+        cmp_res = cmp(value1, value2)
+        if cmp_res==0:
+            cmp_size = cmp(len(value1_list), len(value2_list))
+            if cmp_size==0:
+                name1 = model.get_value(row1, RecipeListModel.COL_NAME)
+                name2 = model.get_value(row2, RecipeListModel.COL_NAME)
+                return cmp(name1,name2)
+            else:
+                return cmp_size
+        else:
+            return cmp_res
+
+    def convert_vpath_to_path(self, view_model, view_path):
+        filtered_model_path = view_model.convert_path_to_child_path(view_path)
+        filtered_model = view_model.get_model()
+
+        # get the path of the original model
+        path = filtered_model.convert_path_to_child_path(filtered_model_path)
+        return path
+
+    def convert_path_to_vpath(self, view_model, path):
+        it = view_model.get_iter_first()
+        while it:
+            name = self.find_item_for_path(path)
+            view_name = view_model.get_value(it, RecipeListModel.COL_NAME)
+            if view_name == name:
+                view_path = view_model.get_path(it)
+                return view_path
+            it = view_model.iter_next(it)
+        return None
+
+    """
+    The populate() function takes as input the data from a
+    bb.event.TargetsTreeGenerated event and populates the RecipeList.
+    """
+    def populate(self, event_model):
+        # First clear the model, in case repopulating
+        self.clear()
+
+        # dummy image for prompt
+        self.set_in_list(self.__custom_image__,  "Use 'Edit image recipe' to customize recipes and packages " \
+                                "to be included in your image ")
+
+        for item in event_model["pn"]:
+            name = item
+            desc = event_model["pn"][item]["description"]
+            lic = event_model["pn"][item]["license"]
+            group = event_model["pn"][item]["section"]
+            inherits = event_model["pn"][item]["inherits"]
+            summary = event_model["pn"][item]["summary"]
+            version = event_model["pn"][item]["version"]
+            revision = event_model["pn"][item]["prevision"]
+            homepage = event_model["pn"][item]["homepage"]
+            bugtracker = event_model["pn"][item]["bugtracker"]
+            filename = event_model["pn"][item]["filename"]
+            install = []
+
+            depends = event_model["depends"].get(item, []) + event_model["rdepends-pn"].get(item, [])
+
+            if ('packagegroup.bbclass' in " ".join(inherits)):
+                atype = 'packagegroup'
+            elif ('/image.bbclass' in " ".join(inherits)):
+                if "edited" not in name:
+                    atype = 'image'
+                    install = event_model["rdepends-pkg"].get(item, []) + event_model["rrecs-pkg"].get(item, [])
+            elif ('meta-' in name):
+                atype = 'toolchain'
+            elif (name == 'dummy-image' or name == 'dummy-toolchain'):
+                atype = 'dummy'
+            else:
+                atype = 'recipe'
+
+            self.set(self.append(), self.COL_NAME, item, self.COL_DESC, desc,
+                     self.COL_LIC, lic, self.COL_GROUP, group,
+                     self.COL_DEPS, " ".join(depends), self.COL_BINB, "",
+                     self.COL_TYPE, atype, self.COL_INC, False,
+                     self.COL_IMG, False, self.COL_INSTALL, " ".join(install), self.COL_PN, item,
+                     self.COL_SUMMARY, summary, self.COL_VERSION, version, self.COL_REVISION, revision,
+                     self.COL_HOMEPAGE, homepage, self.COL_BUGTRACKER, bugtracker,
+                     self.COL_FILE, filename)
+
+        self.pn_path = {}
+        it = self.get_iter_first()
+        while it:
+            pn = self.get_value(it, self.COL_NAME)
+            path = self.get_path(it)
+            self.pn_path[pn] = path
+            it = self.iter_next(it)
+
+    def set_in_list(self, item, desc):
+        self.set(self.append(), self.COL_NAME, item,
+                 self.COL_DESC, desc,
+                 self.COL_LIC, "", self.COL_GROUP, "",
+                 self.COL_DEPS, "", self.COL_BINB, "",
+                 self.COL_TYPE, "image", self.COL_INC, False,
+                 self.COL_IMG, False, self.COL_INSTALL, "", self.COL_PN, item,
+                 self.COL_SUMMARY, "", self.COL_VERSION, "", self.COL_REVISION, "",
+                 self.COL_HOMEPAGE, "", self.COL_BUGTRACKER, "")
+        self.pn_path = {}
+        it = self.get_iter_first()
+        while it:
+            pn = self.get_value(it, self.COL_NAME)
+            path = self.get_path(it)
+            self.pn_path[pn] = path
+            it = self.iter_next(it)
+
+    """
+    Update the model, send out the notification.
+    """
+    def selection_change_notification(self):
+        self.emit("recipe-selection-changed")
+
+    def path_included(self, item_path):
+        return self[item_path][self.COL_INC]
+
+    """
+    Add this item, and any of its dependencies, to the image contents
+    """
+    def include_item(self, item_path, binb="", image_contents=False):
+        if self.path_included(item_path):
+            return
+
+        item_name = self[item_path][self.COL_NAME]
+        item_deps = self[item_path][self.COL_DEPS]
+
+        self[item_path][self.COL_INC] = True
+
+        item_bin = self[item_path][self.COL_BINB].split(', ')
+        if binb and not binb in item_bin:
+            item_bin.append(binb)
+            self[item_path][self.COL_BINB] = ', '.join(item_bin).lstrip(', ')
+
+        # We want to do some magic with things which are brought in by the
+        # base image so tag them as so
+        if image_contents:
+            self[item_path][self.COL_IMG] = True
+
+        if item_deps:
+            # Ensure all of the items deps are included and, where appropriate,
+            # add this item to their COL_BINB
+            for dep in item_deps.split(" "):
+                # If the contents model doesn't already contain dep, add it
+                dep_path = self.find_path_for_item(dep)
+                if not dep_path:
+                    continue
+                dep_included = self.path_included(dep_path)
+
+                if dep_included and not dep in item_bin:
+                    # don't set the COL_BINB to this item if the target is an
+                    # item in our own COL_BINB
+                    dep_bin = self[dep_path][self.COL_BINB].split(', ')
+                    if not item_name in dep_bin:
+                        dep_bin.append(item_name)
+                        self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
+                elif not dep_included:
+                    self.include_item(dep_path, binb=item_name, image_contents=image_contents)
+        dep_bin = self[item_path][self.COL_BINB].split(', ')
+        if self[item_path][self.COL_NAME] in dep_bin:
+            dep_bin.remove(self[item_path][self.COL_NAME])
+        self[item_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
+
+    def exclude_item(self, item_path):
+        if not self.path_included(item_path):
+            return
+
+        self[item_path][self.COL_INC] = False
+
+        item_name = self[item_path][self.COL_NAME]
+        item_deps = self[item_path][self.COL_DEPS]
+        if item_deps:
+            for dep in item_deps.split(" "):
+                dep_path = self.find_path_for_item(dep)
+                if not dep_path:
+                    continue
+                dep_bin = self[dep_path][self.COL_BINB].split(', ')
+                if item_name in dep_bin:
+                    dep_bin.remove(item_name)
+                    self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
+
+        item_bin = self[item_path][self.COL_BINB].split(', ')
+        if item_bin:
+            for binb in item_bin:
+                binb_path = self.find_path_for_item(binb)
+                if not binb_path:
+                    continue
+                self.exclude_item(binb_path)
+
+    def reset(self):
+        it = self.get_iter_first()
+        while it:
+            self.set(it,
+                     self.COL_INC, False,
+                     self.COL_BINB, "",
+                     self.COL_IMG, False)
+            it = self.iter_next(it)
+
+        self.selection_change_notification()
+
+    """
+    Returns two lists. One of user selected recipes and the other containing
+    all selected recipes
+    """
+    def get_selected_recipes(self):
+        allrecipes = []
+        userrecipes = []
+
+        it = self.get_iter_first()
+        while it:
+            if self.get_value(it, self.COL_INC):
+                name = self.get_value(it, self.COL_PN)
+                type = self.get_value(it, self.COL_TYPE)
+                if type != "image":
+                    allrecipes.append(name)
+                    sel = "User Selected" in self.get_value(it, self.COL_BINB)
+                    if sel:
+                        userrecipes.append(name)
+            it = self.iter_next(it)
+
+        return list(set(userrecipes)), list(set(allrecipes))
+
+    def set_selected_recipes(self, recipelist):
+        for pn in recipelist:
+            if pn in self.pn_path.keys():
+                path = self.pn_path[pn]
+                self.include_item(item_path=path,
+                                  binb="User Selected")
+        self.selection_change_notification()
+
+    def get_selected_image(self):
+        it = self.get_iter_first()
+        while it:
+            if self.get_value(it, self.COL_INC):
+                name = self.get_value(it, self.COL_PN)
+                type = self.get_value(it, self.COL_TYPE)
+                if type == "image":
+                    sel = "User Selected" in self.get_value(it, self.COL_BINB)
+                    if sel:
+                        return name
+            it = self.iter_next(it)
+        return None
+
+    def set_selected_image(self, img):
+        if not img:
+            return
+        self.reset()
+        path = self.find_path_for_item(img)
+        self.include_item(item_path=path,
+                          binb="User Selected",
+                          image_contents=True)
+        self.selection_change_notification()
+
+    def set_custom_image_version(self, version):
+        self.custom_image_version = version
+
+    def get_custom_image_version(self):
+        return self.custom_image_version
+
+    def is_custom_image(self):
+        return self.get_selected_image() == self.__custom_image__
diff --git a/bitbake/lib/bb/ui/crumbs/hobpages.py b/bitbake/lib/bb/ui/crumbs/hobpages.py
new file mode 100755
index 0000000..0fd3598
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hobpages.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2012        Intel Corporation
+#
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+from bb.ui.crumbs.hobcolor import HobColors
+from bb.ui.crumbs.hobwidget import hwc
+
+#
+# HobPage: the super class for all Hob-related pages
+#    
+class HobPage (gtk.VBox):
+
+    def __init__(self, builder, title = None):
+        super(HobPage, self).__init__(False, 0)
+        self.builder = builder
+        self.builder_width, self.builder_height = self.builder.size_request()
+
+        if not title:
+            self.title = "Hob -- Image Creator"
+        else:
+            self.title = title
+        self.title_label = gtk.Label()
+
+        self.box_group_area = gtk.VBox(False, 12)
+        self.box_group_area.set_size_request(self.builder_width - 73 - 73, self.builder_height - 88 - 15 - 15)
+        self.group_align = gtk.Alignment(xalign = 0, yalign=0.5, xscale=1, yscale=1)
+        self.group_align.set_padding(15, 15, 73, 73)
+        self.group_align.add(self.box_group_area)
+        self.box_group_area.set_homogeneous(False)
+
+    def set_title(self, title):
+        self.title = title
+        self.title_label.set_markup("<span size='x-large'>%s</span>" % self.title)
+
+    def add_onto_top_bar(self, widget = None, padding = 0):
+        # the top button occupies 1/7 of the page height
+        # setup an event box
+        eventbox = gtk.EventBox()
+        style = eventbox.get_style().copy()
+        style.bg[gtk.STATE_NORMAL] = eventbox.get_colormap().alloc_color(HobColors.LIGHT_GRAY, False, False)
+        eventbox.set_style(style)
+        eventbox.set_size_request(-1, 88)
+
+        hbox = gtk.HBox()
+
+        self.title_label = gtk.Label()
+        self.title_label.set_markup("<span size='x-large'>%s</span>" % self.title)
+        hbox.pack_start(self.title_label, expand=False, fill=False, padding=20)
+
+        if widget:
+            # add the widget in the event box
+            hbox.pack_end(widget, expand=False, fill=False, padding=padding)
+        eventbox.add(hbox)
+
+        return eventbox
+
+    def span_tag(self, size="medium", weight="normal", forground="#1c1c1c"):
+        span_tag = "weight='%s' foreground='%s' size='%s'" % (weight, forground, size)
+        return span_tag
+
+    def append_toolbar_button(self, toolbar, buttonname, icon_disp, icon_hovor, tip, cb):
+        # Create a button and append it on the toolbar according to button name
+        icon = gtk.Image()
+        icon_display = icon_disp
+        icon_hover = icon_hovor
+        pix_buffer = gtk.gdk.pixbuf_new_from_file(icon_display)
+        icon.set_from_pixbuf(pix_buffer)
+        tip_text = tip
+        button = toolbar.append_item(buttonname, tip, None, icon, cb)
+        return button
+
+    @staticmethod
+    def _size_to_string(size):
+        try:
+            if not size:
+                size_str = "0 B"
+            else:
+                if len(str(int(size))) > 6:
+                    size_str = '%.1f' % (size*1.0/(1024*1024)) + ' MB'
+                elif len(str(int(size))) > 3:
+                    size_str = '%.1f' % (size*1.0/1024) + ' KB'
+                else:
+                    size_str = str(size) + ' B'
+        except:
+            size_str = "0 B"
+        return size_str
+
+    @staticmethod
+    def _string_to_size(str_size):
+        try:
+            if not str_size:
+                size = 0
+            else:
+                unit = str_size.split()
+                if len(unit) > 1:
+                    if unit[1] == 'MB':
+                        size = float(unit[0])*1024*1024
+                    elif unit[1] == 'KB':
+                        size = float(unit[0])*1024
+                    elif unit[1] == 'B':
+                        size = float(unit[0])
+                    else:
+                        size = 0
+                else:
+                    size = float(unit[0])
+        except:
+            size = 0
+        return size
+
diff --git a/bitbake/lib/bb/ui/crumbs/hobwidget.py b/bitbake/lib/bb/ui/crumbs/hobwidget.py
new file mode 100644
index 0000000..2b969c1
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/hobwidget.py
@@ -0,0 +1,904 @@
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011-2012   Intel Corporation
+#
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+import gtk
+import gobject
+import os
+import os.path
+import sys
+import pango, pangocairo
+import cairo
+import math
+
+from bb.ui.crumbs.hobcolor import HobColors
+from bb.ui.crumbs.persistenttooltip import PersistentTooltip
+
+class hwc:
+
+    MAIN_WIN_WIDTH   = 1024
+    MAIN_WIN_HEIGHT  = 700
+
+class hic:
+
+    HOB_ICON_BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ("ui/icons/"))
+
+    ICON_RCIPE_DISPLAY_FILE       = os.path.join(HOB_ICON_BASE_DIR, ('recipe/recipe_display.png'))
+    ICON_RCIPE_HOVER_FILE         = os.path.join(HOB_ICON_BASE_DIR, ('recipe/recipe_hover.png'))
+    ICON_PACKAGES_DISPLAY_FILE    = os.path.join(HOB_ICON_BASE_DIR, ('packages/packages_display.png'))
+    ICON_PACKAGES_HOVER_FILE      = os.path.join(HOB_ICON_BASE_DIR, ('packages/packages_hover.png'))
+    ICON_LAYERS_DISPLAY_FILE      = os.path.join(HOB_ICON_BASE_DIR, ('layers/layers_display.png'))
+    ICON_LAYERS_HOVER_FILE        = os.path.join(HOB_ICON_BASE_DIR, ('layers/layers_hover.png'))
+    ICON_IMAGES_DISPLAY_FILE      = os.path.join(HOB_ICON_BASE_DIR, ('images/images_display.png'))
+    ICON_IMAGES_HOVER_FILE        = os.path.join(HOB_ICON_BASE_DIR, ('images/images_hover.png'))
+    ICON_SETTINGS_DISPLAY_FILE    = os.path.join(HOB_ICON_BASE_DIR, ('settings/settings_display.png'))
+    ICON_SETTINGS_HOVER_FILE      = os.path.join(HOB_ICON_BASE_DIR, ('settings/settings_hover.png'))
+    ICON_INFO_DISPLAY_FILE        = os.path.join(HOB_ICON_BASE_DIR, ('info/info_display.png'))
+    ICON_INFO_HOVER_FILE          = os.path.join(HOB_ICON_BASE_DIR, ('info/info_hover.png'))
+    ICON_INDI_CONFIRM_FILE        = os.path.join(HOB_ICON_BASE_DIR, ('indicators/confirmation.png'))
+    ICON_INDI_ERROR_FILE          = os.path.join(HOB_ICON_BASE_DIR, ('indicators/denied.png'))
+    ICON_INDI_REMOVE_FILE         = os.path.join(HOB_ICON_BASE_DIR, ('indicators/remove.png'))
+    ICON_INDI_REMOVE_HOVER_FILE   = os.path.join(HOB_ICON_BASE_DIR, ('indicators/remove-hover.png'))
+    ICON_INDI_ADD_FILE            = os.path.join(HOB_ICON_BASE_DIR, ('indicators/add.png'))
+    ICON_INDI_ADD_HOVER_FILE      = os.path.join(HOB_ICON_BASE_DIR, ('indicators/add-hover.png'))
+    ICON_INDI_REFRESH_FILE        = os.path.join(HOB_ICON_BASE_DIR, ('indicators/refresh.png'))
+    ICON_INDI_ALERT_FILE          = os.path.join(HOB_ICON_BASE_DIR, ('indicators/alert.png'))
+    ICON_INDI_TICK_FILE           = os.path.join(HOB_ICON_BASE_DIR, ('indicators/tick.png'))
+    ICON_INDI_INFO_FILE           = os.path.join(HOB_ICON_BASE_DIR, ('indicators/info.png'))
+
+class HobViewTable (gtk.VBox):
+    """
+    A VBox to contain the table for different recipe views and package view
+    """
+    __gsignals__ = {
+         "toggled"       : (gobject.SIGNAL_RUN_LAST,
+                            gobject.TYPE_NONE,
+                           (gobject.TYPE_PYOBJECT,
+                            gobject.TYPE_STRING,
+                            gobject.TYPE_INT,
+                            gobject.TYPE_PYOBJECT,)),
+         "row-activated" : (gobject.SIGNAL_RUN_LAST,
+                            gobject.TYPE_NONE,
+                           (gobject.TYPE_PYOBJECT,
+                            gobject.TYPE_PYOBJECT,)),
+         "cell-fadeinout-stopped" : (gobject.SIGNAL_RUN_LAST,
+                            gobject.TYPE_NONE,
+                           (gobject.TYPE_PYOBJECT,
+                            gobject.TYPE_PYOBJECT,
+                            gobject.TYPE_PYOBJECT,)),
+    }
+
+    def __init__(self, columns, name):
+        gtk.VBox.__init__(self, False, 6)
+        self.table_tree = gtk.TreeView()
+        self.table_tree.set_headers_visible(True)
+        self.table_tree.set_headers_clickable(True)
+        self.table_tree.set_rules_hint(True)
+        self.table_tree.set_enable_tree_lines(True)
+        self.table_tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
+        self.toggle_columns = []
+        self.table_tree.connect("row-activated", self.row_activated_cb)
+        self.top_bar = None
+        self.tab_name = name
+
+        for i, column in enumerate(columns):
+            col_name = column['col_name']
+            col = gtk.TreeViewColumn(col_name)
+            col.set_clickable(True)
+            col.set_resizable(True)
+            if self.tab_name.startswith('Included'):
+                if col_name!='Included':
+                    col.set_sort_column_id(column['col_id'])
+            else:
+                col.set_sort_column_id(column['col_id'])
+            if 'col_min' in column.keys():
+                col.set_min_width(column['col_min'])
+            if 'col_max' in column.keys():
+                col.set_max_width(column['col_max'])
+            if 'expand' in column.keys():
+                col.set_expand(True)
+            self.table_tree.append_column(col)
+
+            if (not 'col_style' in column.keys()) or column['col_style'] == 'text':
+                cell = gtk.CellRendererText()
+                col.pack_start(cell, True)
+                col.set_attributes(cell, text=column['col_id'])
+                if 'col_t_id' in column.keys():
+                    col.add_attribute(cell, 'font', column['col_t_id'])
+            elif column['col_style'] == 'check toggle':
+                cell = HobCellRendererToggle()
+                cell.set_property('activatable', True)
+                cell.connect("toggled", self.toggled_cb, i, self.table_tree)
+                cell.connect_render_state_changed(self.stop_cell_fadeinout_cb, self.table_tree)
+                self.toggle_id = i
+                col.pack_end(cell, True)
+                col.set_attributes(cell, active=column['col_id'])
+                self.toggle_columns.append(col_name)
+                if 'col_group' in column.keys():
+                    col.set_cell_data_func(cell, self.set_group_number_cb)
+            elif column['col_style'] == 'radio toggle':
+                cell = gtk.CellRendererToggle()
+                cell.set_property('activatable', True)
+                cell.set_radio(True)
+                cell.connect("toggled", self.toggled_cb, i, self.table_tree)
+                self.toggle_id = i
+                col.pack_end(cell, True)
+                col.set_attributes(cell, active=column['col_id'])
+                self.toggle_columns.append(col_name)
+            elif column['col_style'] == 'binb':
+                cell = gtk.CellRendererText()
+                col.pack_start(cell, True)
+                col.set_cell_data_func(cell, self.display_binb_cb, column['col_id'])
+                if 'col_t_id' in column.keys():
+                    col.add_attribute(cell, 'font', column['col_t_id'])
+
+        self.scroll = gtk.ScrolledWindow()
+        self.scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+        self.scroll.add(self.table_tree)
+
+        self.pack_end(self.scroll, True, True, 0)
+
+    def add_no_result_bar(self, entry):
+        color = HobColors.KHAKI
+        self.top_bar = gtk.EventBox()
+        self.top_bar.set_size_request(-1, 70)
+        self.top_bar.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
+        self.top_bar.set_flags(gtk.CAN_DEFAULT)
+        self.top_bar.grab_default()
+
+        no_result_tab = gtk.Table(5, 20, True)
+        self.top_bar.add(no_result_tab)
+
+        label = gtk.Label()
+        label.set_alignment(0.0, 0.5)
+        title = "No results matching your search"
+        label.set_markup("<span size='x-large'><b>%s</b></span>" % title)
+        no_result_tab.attach(label, 1, 14, 1, 4)
+
+        clear_button = HobButton("Clear search")
+        clear_button.set_tooltip_text("Clear search query")
+        clear_button.connect('clicked', self.set_search_entry_clear_cb, entry)
+        no_result_tab.attach(clear_button, 16, 19, 1, 4)
+
+        self.pack_start(self.top_bar, False, True, 12)
+        self.top_bar.show_all()
+
+    def set_search_entry_clear_cb(self, button, search):
+        if search.get_editable() == True:
+            search.set_text("")
+        search.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False)
+        search.grab_focus()
+
+    def display_binb_cb(self, col, cell, model, it, col_id):
+        binb =  model.get_value(it, col_id)
+        # Just display the first item
+        if binb:
+            bin = binb.split(', ')
+            total_no = len(bin)
+            if total_no > 1 and bin[0] == "User Selected":
+                if total_no > 2:
+                    present_binb = bin[1] + ' (+' + str(total_no - 1) + ')'
+                else:
+                    present_binb = bin[1]
+            else:
+                if total_no > 1:
+                    present_binb = bin[0] + ' (+' + str(total_no - 1) + ')'
+                else:
+                    present_binb = bin[0]
+            cell.set_property('text', present_binb)
+        else:
+            cell.set_property('text', "")
+        return True
+
+    def set_model(self, tree_model):
+        self.table_tree.set_model(tree_model)
+
+    def toggle_default(self):
+        model = self.table_tree.get_model()
+        if not model:
+            return
+        iter = model.get_iter_first()
+        if iter:
+            rowpath = model.get_path(iter)
+            model[rowpath][self.toggle_id] = True
+
+    def toggled_cb(self, cell, path, columnid, tree):
+        self.emit("toggled", cell, path, columnid, tree)
+
+    def row_activated_cb(self, tree, path, view_column):
+        if not view_column.get_title() in self.toggle_columns:
+            self.emit("row-activated", tree.get_model(), path)
+
+    def stop_cell_fadeinout_cb(self, ctrl, cell, tree):
+        self.emit("cell-fadeinout-stopped", ctrl, cell, tree)
+
+    def set_group_number_cb(self, col, cell, model, iter):
+        if model and (model.iter_parent(iter) == None):
+            cell.cell_attr["number_of_children"] = model.iter_n_children(iter)
+        else:
+            cell.cell_attr["number_of_children"] = 0
+
+    def connect_group_selection(self, cb_func):
+        self.table_tree.get_selection().connect("changed", cb_func)
+
+"""
+A method to calculate a softened value for the colour of widget when in the
+provided state.
+
+widget: the widget whose style to use
+state: the state of the widget to use the style for
+
+Returns a string value representing the softened colour
+"""
+def soften_color(widget, state=gtk.STATE_NORMAL):
+    # this colour munging routine is heavily inspired bu gdu_util_get_mix_color()
+    # from gnome-disk-utility:
+    # http://git.gnome.org/browse/gnome-disk-utility/tree/src/gdu-gtk/gdu-gtk.c?h=gnome-3-0
+    blend = 0.7
+    style = widget.get_style()
+    color = style.text[state]
+    color.red = color.red * blend + style.base[state].red * (1.0 - blend)
+    color.green = color.green * blend + style.base[state].green * (1.0 - blend)
+    color.blue = color.blue * blend + style.base[state].blue * (1.0 - blend)
+    return color.to_string()
+
+class BaseHobButton(gtk.Button):
+    """
+    A gtk.Button subclass which follows the visual design of Hob for primary
+    action buttons
+
+    label: the text to display as the button's label
+    """
+    def __init__(self, label):
+        gtk.Button.__init__(self, label)
+        HobButton.style_button(self)
+
+    @staticmethod
+    def style_button(button):
+        style = button.get_style()
+        style = gtk.rc_get_style_by_paths(gtk.settings_get_default(), 'gtk-button', 'gtk-button', gobject.TYPE_NONE)
+
+        button.set_flags(gtk.CAN_DEFAULT)
+        button.grab_default()
+
+#        label = "<span size='x-large'><b>%s</b></span>" % gobject.markup_escape_text(button.get_label())
+        label = button.get_label()
+        button.set_label(label)
+        button.child.set_use_markup(True)
+
+class HobButton(BaseHobButton):
+    """
+    A gtk.Button subclass which follows the visual design of Hob for primary
+    action buttons
+
+    label: the text to display as the button's label
+    """
+    def __init__(self, label):
+        BaseHobButton.__init__(self, label)
+        HobButton.style_button(self)
+
+class HobAltButton(BaseHobButton):
+    """
+    A gtk.Button subclass which has no relief, and so is more discrete
+    """
+    def __init__(self, label):
+        BaseHobButton.__init__(self, label)
+        HobAltButton.style_button(self)
+
+    """
+    A callback for the state-changed event to ensure the text is displayed
+    differently when the widget is not sensitive
+    """
+    @staticmethod
+    def desensitise_on_state_change_cb(button, state):
+        if not button.get_property("sensitive"):
+            HobAltButton.set_text(button, False)
+        else:
+            HobAltButton.set_text(button, True)
+
+    """
+    Set the button label with an appropriate colour for the current widget state
+    """
+    @staticmethod
+    def set_text(button, sensitive=True):
+        if sensitive:
+            colour = HobColors.PALE_BLUE
+        else:
+            colour = HobColors.LIGHT_GRAY
+        button.set_label("<span size='large' color='%s'><b>%s</b></span>" % (colour, gobject.markup_escape_text(button.text)))
+        button.child.set_use_markup(True)
+
+class HobImageButton(gtk.Button):
+    """
+    A gtk.Button with an icon and two rows of text, the second of which is
+    displayed in a blended colour.
+
+    primary_text: the main button label
+    secondary_text: optional second line of text
+    icon_path: path to the icon file to display on the button
+    """
+    def __init__(self, primary_text, secondary_text="", icon_path="", hover_icon_path=""):
+        gtk.Button.__init__(self)
+        self.set_relief(gtk.RELIEF_NONE)
+
+        self.icon_path = icon_path
+        self.hover_icon_path = hover_icon_path
+
+        hbox = gtk.HBox(False, 10)
+        hbox.show()
+        self.add(hbox)
+        self.icon = gtk.Image()
+        self.icon.set_from_file(self.icon_path)
+        self.icon.set_alignment(0.5, 0.0)
+        self.icon.show()
+        if self.hover_icon_path and len(self.hover_icon_path):
+            self.connect("enter-notify-event", self.set_hover_icon_cb)
+            self.connect("leave-notify-event", self.set_icon_cb)
+        hbox.pack_start(self.icon, False, False, 0)
+        label = gtk.Label()
+        label.set_alignment(0.0, 0.5)
+        colour = soften_color(label)
+        mark = "<span size='x-large'>%s</span>\n<span size='medium' fgcolor='%s' weight='ultralight'>%s</span>" % (primary_text, colour, secondary_text)
+        label.set_markup(mark)
+        label.show()
+        hbox.pack_start(label, True, True, 0)
+
+    def set_hover_icon_cb(self, widget, event):
+        self.icon.set_from_file(self.hover_icon_path)
+
+    def set_icon_cb(self, widget, event):
+        self.icon.set_from_file(self.icon_path)
+
+class HobInfoButton(gtk.EventBox):
+    """
+    This class implements a button-like widget per the Hob visual and UX designs
+    which will display a persistent tooltip, with the contents of tip_markup, when
+    clicked.
+
+    tip_markup: the Pango Markup to be displayed in the persistent tooltip
+    """
+    def __init__(self, tip_markup, parent=None):
+        gtk.EventBox.__init__(self)
+        self.image = gtk.Image()
+        self.image.set_from_file(
+        hic.ICON_INFO_DISPLAY_FILE)
+        self.image.show()
+        self.add(self.image)
+        self.tip_markup = tip_markup
+        self.my_parent = parent
+
+        self.set_events(gtk.gdk.BUTTON_RELEASE |
+                        gtk.gdk.ENTER_NOTIFY_MASK |
+                        gtk.gdk.LEAVE_NOTIFY_MASK)
+
+        self.connect("button-release-event", self.button_release_cb)
+        self.connect("enter-notify-event", self.mouse_in_cb)
+        self.connect("leave-notify-event", self.mouse_out_cb)
+
+    """
+    When the mouse click is released emulate a button-click and show the associated
+    PersistentTooltip
+    """
+    def button_release_cb(self, widget, event):
+        from bb.ui.crumbs.hig.propertydialog import PropertyDialog
+        self.dialog = PropertyDialog(title = '',
+                    parent = self.my_parent,
+                    information = self.tip_markup,
+                    flags = gtk.DIALOG_DESTROY_WITH_PARENT
+                        | gtk.DIALOG_NO_SEPARATOR)
+
+        button = self.dialog.add_button("Close", gtk.RESPONSE_CANCEL)
+        HobAltButton.style_button(button)
+        button.connect("clicked", lambda w: self.dialog.destroy())
+        self.dialog.show_all()
+        self.dialog.run()
+
+    """
+    Change to the prelight image when the mouse enters the widget
+    """
+    def mouse_in_cb(self, widget, event):
+        self.image.set_from_file(hic.ICON_INFO_HOVER_FILE)
+
+    """
+    Change to the stock image when the mouse enters the widget
+    """
+    def mouse_out_cb(self, widget, event):
+        self.image.set_from_file(hic.ICON_INFO_DISPLAY_FILE)
+
+class HobIndicator(gtk.DrawingArea):
+    def __init__(self, count):
+        gtk.DrawingArea.__init__(self)
+        # Set no window for transparent background
+        self.set_has_window(False)
+        self.set_size_request(38,38)
+        # We need to pass through button clicks
+        self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK)
+
+        self.connect('expose-event', self.expose)
+
+        self.count = count
+        self.color = HobColors.GRAY
+
+    def expose(self, widget, event):
+        if self.count and self.count > 0:
+            ctx = widget.window.cairo_create()
+
+            x, y, w, h = self.allocation
+
+            ctx.set_operator(cairo.OPERATOR_OVER)
+            ctx.set_source_color(gtk.gdk.color_parse(self.color))
+            ctx.translate(w/2, h/2)
+            ctx.arc(x, y, min(w,h)/2 - 2, 0, 2*math.pi)
+            ctx.fill_preserve()
+
+            layout = self.create_pango_layout(str(self.count))
+            textw, texth = layout.get_pixel_size()
+            x = (w/2)-(textw/2) + x
+            y = (h/2) - (texth/2) + y
+            ctx.move_to(x, y)
+            self.window.draw_layout(self.style.light_gc[gtk.STATE_NORMAL], int(x), int(y), layout)
+
+    def set_count(self, count):
+        self.count = count
+
+    def set_active(self, active):
+        if active:
+            self.color = HobColors.DEEP_RED
+        else:
+            self.color = HobColors.GRAY
+
+class HobTabLabel(gtk.HBox):
+    def __init__(self, text, count=0):
+        gtk.HBox.__init__(self, False, 0)
+        self.indicator = HobIndicator(count)
+        self.indicator.show()
+        self.pack_end(self.indicator, False, False)
+        self.lbl = gtk.Label(text)
+        self.lbl.set_alignment(0.0, 0.5)
+        self.lbl.show()
+        self.pack_end(self.lbl, True, True, 6)
+
+    def set_count(self, count):
+        self.indicator.set_count(count)
+
+    def set_active(self, active=True):
+        self.indicator.set_active(active)
+
+class HobNotebook(gtk.Notebook):
+    def __init__(self):
+        gtk.Notebook.__init__(self)
+        self.set_property('homogeneous', True)
+
+        self.pages = []
+
+        self.search = None
+        self.search_focus = False
+        self.page_changed = False
+
+        self.connect("switch-page", self.page_changed_cb)
+
+        self.show_all()
+
+    def page_changed_cb(self, nb, page, page_num):
+        for p, lbl in enumerate(self.pages):
+            if p == page_num:
+                lbl.set_active()
+            else:
+                lbl.set_active(False)
+
+        if self.search:
+            self.page_changed = True
+            self.reset_entry(self.search, page_num)
+
+    def append_page(self, child, tab_label, tab_tooltip=None):
+        label = HobTabLabel(tab_label)
+        if tab_tooltip:
+            label.set_tooltip_text(tab_tooltip)
+        label.set_active(False)
+        self.pages.append(label)
+        gtk.Notebook.append_page(self, child, label)
+
+    def set_entry(self, names, tips):
+        self.search = gtk.Entry()
+        self.search_names = names
+        self.search_tips = tips
+        style = self.search.get_style()
+        style.text[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(HobColors.GRAY, False, False)
+        self.search.set_style(style)
+        self.search.set_text(names[0])
+        self.search.set_tooltip_text(self.search_tips[0])
+        self.search.props.has_tooltip = True
+
+        self.search.set_editable(False)
+        self.search.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, gtk.STOCK_CLEAR)
+        self.search.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False)
+        self.search.connect("icon-release", self.set_search_entry_clear_cb)
+        self.search.set_width_chars(30)
+        self.search.show()
+
+        self.search.connect("focus-in-event", self.set_search_entry_editable_cb)
+        self.search.connect("focus-out-event", self.set_search_entry_reset_cb)
+        self.set_action_widget(self.search, gtk.PACK_END)
+
+    def show_indicator_icon(self, title, number):
+        for child in self.pages:
+            if child.lbl.get_label() == title:
+                child.set_count(number)
+
+    def hide_indicator_icon(self, title):
+        for child in self.pages:
+            if child.lbl.get_label() == title:
+                child.set_count(0)
+
+    def set_search_entry_editable_cb(self, search, event):
+        self.search_focus = True
+        search.set_editable(True)
+        text = search.get_text()
+        if text in self.search_names:
+            search.set_text("")
+        style = self.search.get_style()
+        style.text[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(HobColors.BLACK, False, False)
+        search.set_style(style)
+
+    def set_search_entry_reset_cb(self, search, event):
+        page_num = self.get_current_page()
+        text = search.get_text()
+        if not text:
+            self.reset_entry(search, page_num)
+
+    def reset_entry(self, entry, page_num):
+        style = entry.get_style()
+        style.text[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(HobColors.GRAY, False, False)
+        entry.set_style(style)
+        entry.set_text(self.search_names[page_num])
+        entry.set_tooltip_text(self.search_tips[page_num])
+        entry.set_editable(False)
+        entry.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False)
+
+    def set_search_entry_clear_cb(self, search, icon_pos, event):
+        if search.get_editable() == True:
+            search.set_text("")
+        search.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False)
+        search.grab_focus()
+
+    def set_page(self, title):
+        for child in self.pages:
+            if child.lbl.get_label() == title:
+                child.grab_focus()
+                self.set_current_page(self.pages.index(child))
+                return
+
+class HobWarpCellRendererText(gtk.CellRendererText):
+    def __init__(self, col_number):
+        gtk.CellRendererText.__init__(self)
+        self.set_property("wrap-mode", pango.WRAP_WORD_CHAR)
+        self.set_property("wrap-width", 300) # default value wrap width is 300
+        self.col_n = col_number
+
+    def do_render(self, window, widget, background_area, cell_area, expose_area, flags):
+        if widget:
+            self.props.wrap_width = self.get_resized_wrap_width(widget, widget.get_column(self.col_n))
+        return gtk.CellRendererText.do_render(self, window, widget, background_area, cell_area, expose_area, flags)
+
+    def get_resized_wrap_width(self, treeview, column):
+        otherCols = []
+        for col in treeview.get_columns():
+            if col != column:
+                otherCols.append(col)
+        adjwidth = treeview.allocation.width - sum(c.get_width() for c in otherCols)
+        adjwidth -= treeview.style_get_property("horizontal-separator") * 4
+        if self.props.wrap_width == adjwidth or adjwidth <= 0:
+                adjwidth = self.props.wrap_width
+        return adjwidth
+
+gobject.type_register(HobWarpCellRendererText)
+
+class HobIconChecker(hic):
+    def set_hob_icon_to_stock_icon(self, file_path, stock_id=""):
+        try:
+            pixbuf = gtk.gdk.pixbuf_new_from_file(file_path)
+        except Exception, e:
+            return None
+
+        if stock_id and (gtk.icon_factory_lookup_default(stock_id) == None):
+            icon_factory = gtk.IconFactory()
+            icon_factory.add_default()
+            icon_factory.add(stock_id, gtk.IconSet(pixbuf))
+            gtk.stock_add([(stock_id, '_label', 0, 0, '')])
+
+            return icon_factory.lookup(stock_id)
+
+        return None
+
+    """
+    For make hob icon consistently by request, and avoid icon view diff by system or gtk version, we use some 'hob icon' to replace the 'gtk icon'.
+    this function check the stock_id and make hob_id to replaced the gtk_id then return it or ""
+    """
+    def check_stock_icon(self, stock_name=""):
+        HOB_CHECK_STOCK_NAME = {
+            ('hic-dialog-info', 'gtk-dialog-info', 'dialog-info')           : self.ICON_INDI_INFO_FILE,
+            ('hic-ok',          'gtk-ok',           'ok')                   : self.ICON_INDI_TICK_FILE,
+            ('hic-dialog-error', 'gtk-dialog-error', 'dialog-error')        : self.ICON_INDI_ERROR_FILE,
+            ('hic-dialog-warning', 'gtk-dialog-warning', 'dialog-warning')  : self.ICON_INDI_ALERT_FILE,
+            ('hic-task-refresh', 'gtk-execute', 'execute')                  : self.ICON_INDI_REFRESH_FILE,
+        }
+        valid_stock_id = stock_name
+        if stock_name:
+            for names, path in HOB_CHECK_STOCK_NAME.iteritems():
+                if stock_name in names:
+                    valid_stock_id = names[0]
+                    if not gtk.icon_factory_lookup_default(valid_stock_id):
+                        self.set_hob_icon_to_stock_icon(path, valid_stock_id)
+
+        return valid_stock_id
+
+class HobCellRendererController(gobject.GObject):
+    (MODE_CYCLE_RUNNING, MODE_ONE_SHORT) = range(2)
+    __gsignals__ = {
+        "run-timer-stopped" : (gobject.SIGNAL_RUN_LAST,
+                                gobject.TYPE_NONE,
+                                ()),
+    }
+    def __init__(self, runningmode=MODE_CYCLE_RUNNING, is_draw_row=False):
+        gobject.GObject.__init__(self)
+        self.timeout_id = None
+        self.current_angle_pos = 0.0
+        self.step_angle = 0.0
+        self.tree_headers_height = 0
+        self.running_cell_areas = []
+        self.running_mode = runningmode
+        self.is_queue_draw_row_area = is_draw_row
+        self.force_stop_enable = False
+
+    def is_active(self):
+        if self.timeout_id:
+            return True
+        else:
+            return False
+
+    def reset_run(self):
+        self.force_stop()
+        self.running_cell_areas = []
+        self.current_angle_pos = 0.0
+        self.step_angle = 0.0
+
+    ''' time_iterval: (1~1000)ms, which will be as the basic interval count for timer
+        init_usrdata: the current data which related the progress-bar will be at
+        min_usrdata: the range of min of user data
+        max_usrdata: the range of max of user data
+        step: each step which you want to progress
+        Note: the init_usrdata should in the range of from min to max, and max should > min
+             step should < (max - min)
+    '''
+    def start_run(self, time_iterval, init_usrdata, min_usrdata, max_usrdata, step, tree):
+        if (not time_iterval) or (not max_usrdata):
+            return
+        usr_range = (max_usrdata - min_usrdata) * 1.0
+        self.current_angle_pos = (init_usrdata * 1.0) / usr_range
+        self.step_angle = (step * 1) / usr_range
+        self.timeout_id = gobject.timeout_add(int(time_iterval),
+        self.make_image_on_progressing_cb, tree)
+        self.tree_headers_height = self.get_treeview_headers_height(tree)
+        self.force_stop_enable = False
+
+    def force_stop(self):
+        self.emit("run-timer-stopped")
+        self.force_stop_enable = True
+        if self.timeout_id:
+            if gobject.source_remove(self.timeout_id):
+                self.timeout_id = None
+
+    def on_draw_pixbuf_cb(self, pixbuf, cr, x, y, img_width, img_height, do_refresh=True):
+        if pixbuf:
+            r = max(img_width/2, img_height/2)
+            cr.translate(x + r, y + r)
+            if do_refresh:
+                cr.rotate(2 * math.pi * self.current_angle_pos)
+
+            cr.set_source_pixbuf(pixbuf, -img_width/2, -img_height/2)
+            cr.paint()
+
+    def on_draw_fadeinout_cb(self, cr, color, x, y, width, height, do_fadeout=True):
+        if do_fadeout:
+            alpha = self.current_angle_pos * 0.8
+        else:
+            alpha = (1.0 - self.current_angle_pos) * 0.8
+
+        cr.set_source_rgba(color.red, color.green, color.blue, alpha)
+        cr.rectangle(x, y, width, height)
+        cr.fill()
+
+    def get_treeview_headers_height(self, tree):
+        if tree and (tree.get_property("headers-visible") == True):
+            height = tree.get_allocation().height - tree.get_bin_window().get_size()[1]
+            return height
+
+        return 0
+
+    def make_image_on_progressing_cb(self, tree):
+        self.current_angle_pos += self.step_angle
+        if self.running_mode == self.MODE_CYCLE_RUNNING:
+            if (self.current_angle_pos >= 1):
+                self.current_angle_pos = 0
+        else:
+            if self.current_angle_pos > 1:
+                self.force_stop()
+                return False
+
+        if self.is_queue_draw_row_area:
+            for path in self.running_cell_areas:
+                rect = tree.get_cell_area(path, tree.get_column(0))
+                row_x, _, row_width, _ = tree.get_visible_rect()
+                tree.queue_draw_area(row_x, rect.y + self.tree_headers_height, row_width, rect.height)
+        else:
+            for rect in self.running_cell_areas:
+                tree.queue_draw_area(rect.x, rect.y + self.tree_headers_height, rect.width, rect.height)
+
+        return (not self.force_stop_enable)
+
+    def append_running_cell_area(self, cell_area):
+        if cell_area and (cell_area not in self.running_cell_areas):
+            self.running_cell_areas.append(cell_area)
+
+    def remove_running_cell_area(self, cell_area):
+        if cell_area in self.running_cell_areas:
+            self.running_cell_areas.remove(cell_area)
+        if not self.running_cell_areas:
+            self.reset_run()
+
+gobject.type_register(HobCellRendererController)
+
+class HobCellRendererPixbuf(gtk.CellRendererPixbuf):
+    def __init__(self):
+        gtk.CellRendererPixbuf.__init__(self)
+        self.control = HobCellRendererController()
+        # add icon checker for make the gtk-icon transfer to hob-icon
+        self.checker = HobIconChecker()
+        self.set_property("stock-size", gtk.ICON_SIZE_DND)
+
+    def get_pixbuf_from_stock_icon(self, widget, stock_id="", size=gtk.ICON_SIZE_DIALOG):
+        if widget and stock_id and gtk.icon_factory_lookup_default(stock_id):
+            return widget.render_icon(stock_id, size)
+
+        return None
+
+    def set_icon_name_to_id(self, new_name):
+        if new_name and type(new_name) == str:
+            # check the name is need to transfer to hob icon or not
+            name = self.checker.check_stock_icon(new_name)
+            if name.startswith("hic") or name.startswith("gtk"):
+                stock_id = name
+            else:
+                stock_id = 'gtk-' + name
+
+        return stock_id
+
+    ''' render cell exactly, "icon-name" is priority
+        if use the 'hic-task-refresh' will make the pix animation
+        if 'pix' will change the pixbuf for it from the pixbuf or image.
+    '''
+    def do_render(self, window, tree, background_area,cell_area, expose_area, flags):
+        if (not self.control) or (not tree):
+            return
+
+        x, y, w, h = self.on_get_size(tree, cell_area)
+        x += cell_area.x
+        y += cell_area.y
+        w -= 2 * self.get_property("xpad")
+        h -= 2 * self.get_property("ypad")
+
+        stock_id = ""
+        if self.props.icon_name:
+            stock_id = self.set_icon_name_to_id(self.props.icon_name)
+        elif self.props.stock_id:
+            stock_id = self.props.stock_id
+        elif self.props.pixbuf:
+            pix = self.props.pixbuf
+        else:
+            return
+
+        if stock_id:
+            pix = self.get_pixbuf_from_stock_icon(tree, stock_id, self.props.stock_size)
+        if stock_id == 'hic-task-refresh':
+            self.control.append_running_cell_area(cell_area)
+            if self.control.is_active():
+                self.control.on_draw_pixbuf_cb(pix, window.cairo_create(), x, y, w, h, True)
+            else:
+                self.control.start_run(200, 0, 0, 1000, 150, tree)
+        else:
+            self.control.remove_running_cell_area(cell_area)
+            self.control.on_draw_pixbuf_cb(pix, window.cairo_create(), x, y, w, h, False)
+
+    def on_get_size(self, widget, cell_area):
+        if self.props.icon_name or self.props.pixbuf or self.props.stock_id:
+            w, h = gtk.icon_size_lookup(self.props.stock_size)
+            calc_width = self.get_property("xpad") * 2 + w
+            calc_height = self.get_property("ypad") * 2 + h
+            x_offset = 0
+            y_offset = 0
+            if cell_area and w > 0 and h > 0:
+                x_offset = self.get_property("xalign") * (cell_area.width - calc_width - self.get_property("xpad"))
+                y_offset = self.get_property("yalign") * (cell_area.height - calc_height - self.get_property("ypad"))
+
+            return x_offset, y_offset, w, h
+
+        return 0, 0, 0, 0
+
+gobject.type_register(HobCellRendererPixbuf)
+
+class HobCellRendererToggle(gtk.CellRendererToggle):
+    def __init__(self):
+        gtk.CellRendererToggle.__init__(self)
+        self.ctrl = HobCellRendererController(is_draw_row=True)
+        self.ctrl.running_mode = self.ctrl.MODE_ONE_SHORT
+        self.cell_attr = {"fadeout": False, "number_of_children": 0}
+
+    def do_render(self, window, widget, background_area, cell_area, expose_area, flags):
+        if (not self.ctrl) or (not widget):
+            return
+
+        if flags & gtk.CELL_RENDERER_SELECTED:
+            state = gtk.STATE_SELECTED
+        else:
+            state = gtk.STATE_NORMAL
+
+        if self.ctrl.is_active():
+            path = widget.get_path_at_pos(cell_area.x + cell_area.width/2, cell_area.y + cell_area.height/2)
+            # sometimes the parameters of cell_area will be a negative number,such as pull up down the scroll bar
+            # it's over the tree container range, so the path will be bad
+            if not path: return
+            path = path[0]
+            if path in self.ctrl.running_cell_areas:
+                cr = window.cairo_create()
+                color = widget.get_style().base[state]
+
+                row_x, _, row_width, _ = widget.get_visible_rect()
+                border_y = self.get_property("ypad")
+                self.ctrl.on_draw_fadeinout_cb(cr, color, row_x, cell_area.y - border_y, row_width, \
+                                               cell_area.height + border_y * 2, self.cell_attr["fadeout"])
+        # draw number of a group
+        if self.cell_attr["number_of_children"]:
+            text = "%d pkg" % self.cell_attr["number_of_children"]
+            pangolayout = widget.create_pango_layout(text)
+            textw, texth = pangolayout.get_pixel_size()
+            x = cell_area.x + (cell_area.width/2) - (textw/2)
+            y = cell_area.y + (cell_area.height/2) - (texth/2)
+
+            widget.style.paint_layout(window, state, True, cell_area, widget, "checkbox", x, y, pangolayout)
+        else:
+            return gtk.CellRendererToggle.do_render(self, window, widget, background_area, cell_area, expose_area, flags)
+
+    '''delay: normally delay time is 1000ms
+       cell_list: whilch cells need to be render
+    '''
+    def fadeout(self, tree, delay, cell_list=None):
+        if (delay < 200) or (not tree):
+            return
+        self.cell_attr["fadeout"] = True
+        self.ctrl.running_cell_areas = cell_list
+        self.ctrl.start_run(200, 0, 0, delay, (delay * 200 / 1000), tree)
+
+    def connect_render_state_changed(self, func, usrdata=None):
+        if not func:
+            return
+        if usrdata:
+            self.ctrl.connect("run-timer-stopped", func, self, usrdata)
+        else:
+            self.ctrl.connect("run-timer-stopped", func, self)
+
+gobject.type_register(HobCellRendererToggle)
diff --git a/bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py b/bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py
new file mode 100644
index 0000000..2766bea
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py
@@ -0,0 +1,561 @@
+#!/usr/bin/env python
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2012        Intel Corporation
+#
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import glib
+import re
+from bb.ui.crumbs.progressbar import HobProgressBar
+from bb.ui.crumbs.hobcolor import HobColors
+from bb.ui.crumbs.hobwidget import hic, HobImageButton, HobInfoButton, HobAltButton, HobButton
+from bb.ui.crumbs.hoblistmodel import RecipeListModel
+from bb.ui.crumbs.hobpages import HobPage
+from bb.ui.crumbs.hig.retrieveimagedialog import RetrieveImageDialog
+
+#
+# ImageConfigurationPage
+#
+class ImageConfigurationPage (HobPage):
+
+    __dummy_machine__ = "--select a machine--"
+    __dummy_image__   = "--select an image recipe--"
+    __custom_image__  = "Select from my image recipes"
+
+    def __init__(self, builder):
+        super(ImageConfigurationPage, self).__init__(builder, "Image configuration")
+
+        self.image_combo_id = None
+        # we use machine_combo_changed_by_manual to identify the machine is changed by code
+        # or by manual. If by manual, all user's recipe selection and package selection are
+        # cleared.
+        self.machine_combo_changed_by_manual = True
+        self.stopping = False
+        self.warning_shift = 0
+        self.custom_image_selected = None
+        self.create_visual_elements()
+
+    def create_visual_elements(self):
+        # create visual elements
+        self.toolbar = gtk.Toolbar()
+        self.toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL)
+        self.toolbar.set_style(gtk.TOOLBAR_BOTH)
+
+        my_images_button = self.append_toolbar_button(self.toolbar,
+            "Images",
+            hic.ICON_IMAGES_DISPLAY_FILE,
+            hic.ICON_IMAGES_HOVER_FILE,
+            "Open previously built images",
+            self.my_images_button_clicked_cb)
+        settings_button = self.append_toolbar_button(self.toolbar,
+            "Settings",
+            hic.ICON_SETTINGS_DISPLAY_FILE,
+            hic.ICON_SETTINGS_HOVER_FILE,
+            "View additional build settings",
+            self.settings_button_clicked_cb)
+
+        self.config_top_button = self.add_onto_top_bar(self.toolbar)
+
+        self.gtable = gtk.Table(40, 40, True)
+        self.create_config_machine()
+        self.create_config_baseimg()
+        self.config_build_button = self.create_config_build_button()
+
+    def _remove_all_widget(self):
+        children = self.gtable.get_children() or []
+        for child in children:
+            self.gtable.remove(child)
+        children = self.box_group_area.get_children() or []
+        for child in children:
+            self.box_group_area.remove(child)
+        children = self.get_children() or []
+        for child in children:
+            self.remove(child)
+
+    def _pack_components(self, pack_config_build_button = False):
+        self._remove_all_widget()
+        self.pack_start(self.config_top_button, expand=False, fill=False)
+        self.pack_start(self.group_align, expand=True, fill=True)
+
+        self.box_group_area.pack_start(self.gtable, expand=True, fill=True)
+        if pack_config_build_button:
+            self.box_group_area.pack_end(self.config_build_button, expand=False, fill=False)
+        else:
+            box = gtk.HBox(False, 6)
+            box.show()
+            subbox = gtk.HBox(False, 0)
+            subbox.set_size_request(205, 49)
+            subbox.show()
+            box.add(subbox)
+            self.box_group_area.pack_end(box, False, False)
+
+    def show_machine(self):
+        self.progress_bar.reset()
+        self._pack_components(pack_config_build_button = False)
+        self.set_config_machine_layout(show_progress_bar = False)
+        self.show_all()
+
+    def update_progress_bar(self, title, fraction, status=None):
+        if self.stopping == False:
+            self.progress_bar.update(fraction)
+            self.progress_bar.set_text(title)
+            self.progress_bar.set_rcstyle(status)
+
+    def show_info_populating(self):
+        self._pack_components(pack_config_build_button = False)
+        self.set_config_machine_layout(show_progress_bar = True)
+        self.show_all()
+
+    def show_info_populated(self):
+        self.progress_bar.reset()
+        self._pack_components(pack_config_build_button = False)
+        self.set_config_machine_layout(show_progress_bar = False)
+        self.set_config_baseimg_layout()
+        self.show_all()
+
+    def show_baseimg_selected(self):
+        self.progress_bar.reset()
+        self._pack_components(pack_config_build_button = True)
+        self.set_config_machine_layout(show_progress_bar = False)
+        self.set_config_baseimg_layout()
+        self.show_all()
+        if self.builder.recipe_model.get_selected_image() == self.builder.recipe_model.__custom_image__:
+            self.just_bake_button.hide()
+
+    def add_warnings_bar(self):
+        #create the warnings bar shown when recipes parsing generates warnings
+        color = HobColors.KHAKI
+        warnings_bar = gtk.EventBox()
+        warnings_bar.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
+        warnings_bar.set_flags(gtk.CAN_DEFAULT)
+        warnings_bar.grab_default()
+
+        build_stop_tab = gtk.Table(10, 20, True)
+        warnings_bar.add(build_stop_tab)
+
+        icon = gtk.Image()
+        icon_pix_buffer = gtk.gdk.pixbuf_new_from_file(hic.ICON_INDI_ALERT_FILE)
+        icon.set_from_pixbuf(icon_pix_buffer)
+        build_stop_tab.attach(icon, 0, 2, 0, 10)
+
+        label = gtk.Label()
+        label.set_alignment(0.0, 0.5)
+        warnings_nb = len(self.builder.parsing_warnings)
+        if warnings_nb == 1:
+            label.set_markup("<span size='x-large'><b>1 recipe parsing warning</b></span>")
+        else:
+            label.set_markup("<span size='x-large'><b>%s recipe parsing warnings</b></span>" % warnings_nb)
+        build_stop_tab.attach(label, 2, 12, 0, 10)
+
+        view_warnings_button = HobButton("View warnings")
+        view_warnings_button.connect('clicked', self.view_warnings_button_clicked_cb)
+        build_stop_tab.attach(view_warnings_button, 15, 19, 1, 9)
+
+        return warnings_bar
+
+    def disable_warnings_bar(self):
+        if self.builder.parsing_warnings:
+            if hasattr(self, 'warnings_bar'):
+                self.warnings_bar.hide_all()
+            self.builder.parsing_warnings = []
+
+    def create_config_machine(self):
+        self.machine_title = gtk.Label()
+        self.machine_title.set_alignment(0.0, 0.5)
+        mark = "<span %s>Select a machine</span>" % self.span_tag('x-large', 'bold')
+        self.machine_title.set_markup(mark)
+
+        self.machine_title_desc = gtk.Label()
+        self.machine_title_desc.set_alignment(0.0, 0.5)
+        mark = ("<span %s>Your selection is the profile of the target machine for which you"
+        " are building the image.\n</span>") % (self.span_tag('medium'))
+        self.machine_title_desc.set_markup(mark)
+
+        self.machine_combo = gtk.combo_box_new_text()
+        self.machine_combo.connect("changed", self.machine_combo_changed_cb)
+
+        icon_file = hic.ICON_LAYERS_DISPLAY_FILE
+        hover_file = hic.ICON_LAYERS_HOVER_FILE
+        self.layer_button = HobImageButton("Layers", "Add support for machines, software, etc.",
+                                icon_file, hover_file)
+        self.layer_button.connect("clicked", self.layer_button_clicked_cb)
+
+        markup = "Layers are a powerful mechanism to extend the Yocto Project "
+        markup += "with your own functionality.\n"
+        markup += "For more on layers, check the <a href=\""
+        markup += "http://www.yoctoproject.org/docs/current/dev-manual/"
+        markup += "dev-manual.html#understanding-and-using-layers\">reference manual</a>."
+        self.layer_info_icon = HobInfoButton("<b>Layers</b>" + "*" + markup, self.get_parent())
+        self.progress_bar = HobProgressBar()
+        self.stop_button = HobAltButton("Stop")
+        self.stop_button.connect("clicked", self.stop_button_clicked_cb)
+        self.machine_separator = gtk.HSeparator()
+
+    def set_config_machine_layout(self, show_progress_bar = False):
+        self.gtable.attach(self.machine_title, 0, 40, 0, 4)
+        self.gtable.attach(self.machine_title_desc, 0, 40, 4, 6)
+        self.gtable.attach(self.machine_combo, 0, 12, 7, 10)
+        self.gtable.attach(self.layer_button, 14, 36, 7, 12)
+        self.gtable.attach(self.layer_info_icon, 36, 40, 7, 11)
+        if show_progress_bar:
+            #self.gtable.attach(self.progress_box, 0, 40, 15, 18)
+            self.gtable.attach(self.progress_bar, 0, 37, 15, 18)
+            self.gtable.attach(self.stop_button, 37, 40, 15, 18, 0, 0)
+        if self.builder.parsing_warnings:
+            self.warnings_bar = self.add_warnings_bar()
+            self.gtable.attach(self.warnings_bar, 0, 40, 14, 18)
+            self.warning_shift = 4
+        else:
+            self.warning_shift = 0
+        self.gtable.attach(self.machine_separator, 0, 40, 13, 14)
+
+    def create_config_baseimg(self):
+        self.image_title = gtk.Label()
+        self.image_title.set_alignment(0, 1.0)
+        mark = "<span %s>Select an image recipe</span>" % self.span_tag('x-large', 'bold')
+        self.image_title.set_markup(mark)
+
+        self.image_title_desc = gtk.Label()
+        self.image_title_desc.set_alignment(0, 0.5)
+
+        mark = ("<span %s>Image recipes are a starting point for the type of image you want. "
+                "You can build them as \n"
+                "they are or edit them to suit your needs.\n</span>") % self.span_tag('medium')
+        self.image_title_desc.set_markup(mark)
+
+        self.image_combo = gtk.combo_box_new_text()
+        self.image_combo.set_row_separator_func(self.combo_separator_func, None)
+        self.image_combo_id = self.image_combo.connect("changed", self.image_combo_changed_cb)
+
+        self.image_desc = gtk.Label()
+        self.image_desc.set_alignment(0.0, 0.5)
+        self.image_desc.set_size_request(256, -1)
+        self.image_desc.set_justify(gtk.JUSTIFY_LEFT)
+        self.image_desc.set_line_wrap(True)
+
+        # button to view recipes
+        icon_file = hic.ICON_RCIPE_DISPLAY_FILE
+        hover_file = hic.ICON_RCIPE_HOVER_FILE
+        self.view_adv_configuration_button = HobImageButton("Advanced configuration",
+                                                                 "Select image types, package formats, etc",
+                                                                 icon_file, hover_file)        
+        self.view_adv_configuration_button.connect("clicked", self.view_adv_configuration_button_clicked_cb)
+
+        self.image_separator = gtk.HSeparator()
+
+    def combo_separator_func(self, model, iter, user_data):
+        name = model.get_value(iter, 0)
+        if name == "--Separator--":
+            return True
+
+    def set_config_baseimg_layout(self):
+        self.gtable.attach(self.image_title, 0, 40, 15+self.warning_shift, 17+self.warning_shift)
+        self.gtable.attach(self.image_title_desc, 0, 40, 18+self.warning_shift, 22+self.warning_shift)
+        self.gtable.attach(self.image_combo, 0, 12, 23+self.warning_shift, 26+self.warning_shift)
+        self.gtable.attach(self.image_desc, 0, 12, 27+self.warning_shift, 33+self.warning_shift)
+        self.gtable.attach(self.view_adv_configuration_button, 14, 36, 23+self.warning_shift, 28+self.warning_shift)
+        self.gtable.attach(self.image_separator, 0, 40, 35+self.warning_shift, 36+self.warning_shift)
+
+    def create_config_build_button(self):
+        # Create the "Build packages" and "Build image" buttons at the bottom
+        button_box = gtk.HBox(False, 6)
+
+        # create button "Build image"
+        self.just_bake_button = HobButton("Build image")
+        self.just_bake_button.set_tooltip_text("Build the image recipe as it is")
+        self.just_bake_button.connect("clicked", self.just_bake_button_clicked_cb)
+        button_box.pack_end(self.just_bake_button, expand=False, fill=False)
+
+        # create button "Edit image recipe"
+        self.edit_image_button = HobAltButton("Edit image recipe")
+        self.edit_image_button.set_tooltip_text("Customize the recipes and packages to be included in your image")
+        self.edit_image_button.connect("clicked", self.edit_image_button_clicked_cb)
+        button_box.pack_end(self.edit_image_button, expand=False, fill=False)
+
+        return button_box
+
+    def stop_button_clicked_cb(self, button):
+        self.stopping = True
+        self.progress_bar.set_text("Stopping recipe parsing")
+        self.progress_bar.set_rcstyle("stop")
+        self.builder.cancel_parse_sync()
+
+    def view_warnings_button_clicked_cb(self, button):
+        self.builder.show_warning_dialog()
+
+    def machine_combo_changed_idle_cb(self):
+        self.builder.window.set_cursor(None)
+
+    def machine_combo_changed_cb(self, machine_combo):
+        self.stopping = False
+        self.builder.parsing_warnings = []
+        combo_item = machine_combo.get_active_text()
+        if not combo_item or combo_item == self.__dummy_machine__:
+            return
+
+        self.builder.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+        self.builder.wait(0.1) #wait for combo and cursor to update
+
+        # remove __dummy_machine__ item from the store list after first user selection
+        # because it is no longer valid
+        combo_store = machine_combo.get_model()
+        if len(combo_store) and (combo_store[0][0] == self.__dummy_machine__):
+            machine_combo.remove_text(0)
+
+        self.builder.configuration.curr_mach = combo_item
+        if self.machine_combo_changed_by_manual:
+            self.builder.configuration.clear_selection()
+        # reset machine_combo_changed_by_manual
+        self.machine_combo_changed_by_manual = True
+
+        self.builder.configuration.selected_image = None
+
+        # Do reparse recipes
+        self.builder.populate_recipe_package_info_async()
+
+        glib.idle_add(self.machine_combo_changed_idle_cb)
+
+    def update_machine_combo(self):
+        self.disable_warnings_bar()
+        all_machines = [self.__dummy_machine__] + self.builder.parameters.all_machines
+
+        model = self.machine_combo.get_model()
+        model.clear()
+        for machine in all_machines:
+            self.machine_combo.append_text(machine)
+        self.machine_combo.set_active(0)
+
+    def switch_machine_combo(self):
+        self.disable_warnings_bar()
+        self.machine_combo_changed_by_manual = False
+        model = self.machine_combo.get_model()
+        active = 0
+        while active < len(model):
+            if model[active][0] == self.builder.configuration.curr_mach:
+                self.machine_combo.set_active(active)
+                return
+            active += 1
+
+        if model[0][0] != self.__dummy_machine__:
+            self.machine_combo.insert_text(0, self.__dummy_machine__)
+
+        self.machine_combo.set_active(0)
+
+    def update_image_desc(self):
+        desc = ""
+        selected_image = self.image_combo.get_active_text()
+        if selected_image and selected_image in self.builder.recipe_model.pn_path.keys():
+            image_path = self.builder.recipe_model.pn_path[selected_image]
+            image_iter = self.builder.recipe_model.get_iter(image_path)
+            desc = self.builder.recipe_model.get_value(image_iter, self.builder.recipe_model.COL_DESC)
+
+        mark = ("<span %s>%s</span>\n") % (self.span_tag('small'), desc)
+        self.image_desc.set_markup(mark)
+
+    def image_combo_changed_idle_cb(self, selected_image, selected_recipes, selected_packages):
+        self.builder.update_recipe_model(selected_image, selected_recipes)
+        self.builder.update_package_model(selected_packages)
+        self.builder.window_sensitive(True)
+
+    def image_combo_changed_cb(self, combo):
+        self.builder.window_sensitive(False)
+        selected_image = self.image_combo.get_active_text()
+        if selected_image == self.__custom_image__:
+            topdir = self.builder.get_topdir()
+            images_dir = topdir + "/recipes/images/custom/"
+            self.builder.ensure_dir(images_dir)
+
+            dialog = RetrieveImageDialog(images_dir, "Select from my image recipes",
+                            self.builder, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
+            response = dialog.run()
+            if response == gtk.RESPONSE_OK:
+                image_name = dialog.get_filename()
+                head, tail = os.path.split(image_name)
+                selected_image = os.path.splitext(tail)[0]
+                self.custom_image_selected = selected_image
+                self.update_image_combo(self.builder.recipe_model, selected_image)
+            else:
+                selected_image = self.__dummy_image__
+                self.update_image_combo(self.builder.recipe_model, None)
+            dialog.destroy()
+        else:
+            if self.custom_image_selected:
+                self.custom_image_selected = None
+                self.update_image_combo(self.builder.recipe_model, selected_image)
+
+        if not selected_image or (selected_image == self.__dummy_image__):
+            self.builder.window_sensitive(True)
+            self.just_bake_button.hide()
+            self.edit_image_button.hide()
+            return
+
+        # remove __dummy_image__ item from the store list after first user selection
+        # because it is no longer valid
+        combo_store = combo.get_model()
+        if len(combo_store) and (combo_store[0][0] == self.__dummy_image__):
+            combo.remove_text(0)
+
+        self.builder.customized = False
+
+        selected_recipes = []
+
+        image_path = self.builder.recipe_model.pn_path[selected_image]
+        image_iter = self.builder.recipe_model.get_iter(image_path)
+        selected_packages = self.builder.recipe_model.get_value(image_iter, self.builder.recipe_model.COL_INSTALL).split()
+        self.update_image_desc()
+
+        self.builder.recipe_model.reset()
+        self.builder.package_model.reset()
+
+        self.show_baseimg_selected()
+
+        if selected_image == self.builder.recipe_model.__custom_image__:
+            self.just_bake_button.hide()
+
+        glib.idle_add(self.image_combo_changed_idle_cb, selected_image, selected_recipes, selected_packages)
+
+    def _image_combo_connect_signal(self):
+        if not self.image_combo_id:
+            self.image_combo_id = self.image_combo.connect("changed", self.image_combo_changed_cb)
+
+    def _image_combo_disconnect_signal(self):
+        if self.image_combo_id:
+            self.image_combo.disconnect(self.image_combo_id)
+            self.image_combo_id = None
+
+    def update_image_combo(self, recipe_model, selected_image):
+        # Update the image combo according to the images in the recipe_model
+        # populate image combo
+        filter = {RecipeListModel.COL_TYPE : ['image']}
+        image_model = recipe_model.tree_model(filter)
+        image_model.set_sort_column_id(recipe_model.COL_NAME, gtk.SORT_ASCENDING)
+        active = 0
+        cnt = 0
+
+        white_pattern = []
+        if self.builder.parameters.image_white_pattern:
+            for i in self.builder.parameters.image_white_pattern.split():
+                white_pattern.append(re.compile(i))
+
+        black_pattern = []
+        if self.builder.parameters.image_black_pattern:
+            for i in self.builder.parameters.image_black_pattern.split():
+                black_pattern.append(re.compile(i))
+        black_pattern.append(re.compile("hob-image"))
+        black_pattern.append(re.compile("edited(-[0-9]*)*.bb$"))
+
+        it = image_model.get_iter_first()
+        self._image_combo_disconnect_signal()
+        model = self.image_combo.get_model()
+        model.clear()
+        # Set a indicator text to combo store when first open
+        if not selected_image:
+            self.image_combo.append_text(self.__dummy_image__)
+            cnt = cnt + 1
+
+        self.image_combo.append_text(self.__custom_image__)
+        self.image_combo.append_text("--Separator--")
+        cnt = cnt + 2
+
+        topdir = self.builder.get_topdir()
+        # append and set active
+        while it:
+            path = image_model.get_path(it)
+            it = image_model.iter_next(it)
+            image_name = image_model[path][recipe_model.COL_NAME]
+            if image_name == self.builder.recipe_model.__custom_image__:
+                continue
+
+            if black_pattern:
+                allow = True
+                for pattern in black_pattern:
+                    if pattern.search(image_name):
+                        allow = False
+                        break
+            elif white_pattern:
+                allow = False
+                for pattern in white_pattern:
+                    if pattern.search(image_name):
+                        allow = True
+                        break
+            else:
+                allow = True
+
+            file_name = image_model[path][recipe_model.COL_FILE]
+            if file_name and topdir in file_name:
+                allow = False
+
+            if allow:
+                self.image_combo.append_text(image_name)
+                if image_name == selected_image:
+                    active = cnt
+                cnt = cnt + 1
+        self.image_combo.append_text(self.builder.recipe_model.__custom_image__)
+
+        if selected_image == self.builder.recipe_model.__custom_image__:
+            active = cnt
+
+        if self.custom_image_selected:
+            self.image_combo.append_text("--Separator--")
+            self.image_combo.append_text(self.custom_image_selected)
+            cnt = cnt + 2
+            if self.custom_image_selected == selected_image:
+                active = cnt
+
+        self.image_combo.set_active(active)
+
+        if active != 0:
+            self.show_baseimg_selected()
+
+        self._image_combo_connect_signal()
+
+    def layer_button_clicked_cb(self, button):
+        # Create a layer selection dialog
+        self.builder.show_layer_selection_dialog()
+
+    def view_adv_configuration_button_clicked_cb(self, button):
+        # Create an advanced settings dialog
+        response, settings_changed = self.builder.show_adv_settings_dialog()
+        if not response:
+            return
+        if settings_changed:
+            self.builder.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+            self.builder.wait(0.1) #wait for adv_settings_dialog to terminate
+            self.builder.reparse_post_adv_settings()
+            self.builder.window.set_cursor(None)
+
+    def just_bake_button_clicked_cb(self, button):
+        self.builder.parsing_warnings = []
+        self.builder.just_bake()
+
+    def edit_image_button_clicked_cb(self, button):
+        self.builder.set_base_image()
+        self.builder.show_recipes()
+
+    def my_images_button_clicked_cb(self, button):
+        self.builder.show_load_my_images_dialog()
+
+    def settings_button_clicked_cb(self, button):
+        # Create an advanced settings dialog
+        response, settings_changed = self.builder.show_simple_settings_dialog()
+        if not response:
+            return
+        if settings_changed:
+            self.builder.reparse_post_adv_settings()
diff --git a/bitbake/lib/bb/ui/crumbs/imagedetailspage.py b/bitbake/lib/bb/ui/crumbs/imagedetailspage.py
new file mode 100755
index 0000000..352e948
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/imagedetailspage.py
@@ -0,0 +1,669 @@
+#!/usr/bin/env python
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2012        Intel Corporation
+#
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gobject
+import gtk
+from bb.ui.crumbs.hobcolor import HobColors
+from bb.ui.crumbs.hobwidget import hic, HobViewTable, HobAltButton, HobButton
+from bb.ui.crumbs.hobpages import HobPage
+import subprocess
+from bb.ui.crumbs.hig.crumbsdialog import CrumbsDialog
+from bb.ui.crumbs.hig.saveimagedialog import SaveImageDialog
+
+#
+# ImageDetailsPage
+#
+class ImageDetailsPage (HobPage):
+
+    class DetailBox (gtk.EventBox):
+        def __init__(self, widget = None, varlist = None, vallist = None, icon = None, button = None, button2=None, color = HobColors.LIGHT_GRAY):
+            gtk.EventBox.__init__(self)
+
+            # set color
+            style = self.get_style().copy()
+            style.bg[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(color, False, False)
+            self.set_style(style)
+
+            self.row = gtk.Table(1, 2, False)
+            self.row.set_border_width(10)
+            self.add(self.row)
+        
+            total_rows = 0
+            if widget:
+                total_rows = 10
+            if varlist and vallist:
+                # pack the icon and the text on the left
+                total_rows += len(varlist)
+            self.table = gtk.Table(total_rows, 20, True)
+            self.table.set_row_spacings(6)
+            self.table.set_size_request(100, -1)
+            self.row.attach(self.table, 0, 1, 0, 1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=gtk.FILL)
+            
+            colid = 0
+            rowid = 0
+            self.line_widgets = {}
+            if icon:
+                self.table.attach(icon, colid, colid + 2, 0, 1)
+                colid = colid + 2
+            if widget:
+                self.table.attach(widget, colid, 20, 0, 10)
+                rowid = 10
+            if varlist and vallist:
+                for row in range(rowid, total_rows):
+                    index = row - rowid
+                    self.line_widgets[varlist[index]] = self.text2label(varlist[index], vallist[index])
+                    self.table.attach(self.line_widgets[varlist[index]], colid, 20, row, row + 1)
+            # pack the button on the right
+            if button:
+                self.bbox = gtk.VBox()
+                self.bbox.pack_start(button, expand=True, fill=False)
+                if button2:
+                    self.bbox.pack_start(button2, expand=True, fill=False)
+                self.bbox.set_size_request(150,-1)
+                self.row.attach(self.bbox, 1, 2, 0, 1, xoptions=gtk.FILL, yoptions=gtk.EXPAND)
+                
+        def update_line_widgets(self, variable, value):
+            if len(self.line_widgets) == 0:
+                return
+            if not isinstance(self.line_widgets[variable], gtk.Label):
+                return
+            self.line_widgets[variable].set_markup(self.format_line(variable, value))
+
+        def wrap_line(self, inputs):
+            # wrap the long text of inputs
+            wrap_width_chars = 75
+            outputs = ""
+            tmps = inputs
+            less_chars = len(inputs)
+            while (less_chars - wrap_width_chars) > 0:
+                less_chars -= wrap_width_chars
+                outputs += tmps[:wrap_width_chars] + "\n               "
+                tmps = inputs[less_chars:]
+            outputs += tmps
+            return outputs
+
+        def format_line(self, variable, value):
+            wraped_value = self.wrap_line(value)
+            markup = "<span weight=\'bold\'>%s</span>" % variable
+            markup += "<span weight=\'normal\' foreground=\'#1c1c1c\' font_desc=\'14px\'>%s</span>" % wraped_value
+            return markup
+
+        def text2label(self, variable, value):
+            # append the name:value to the left box
+            # such as "Name: hob-core-minimal-variant-2011-12-15-beagleboard"
+            label = gtk.Label()
+            label.set_alignment(0.0, 0.5)
+            label.set_markup(self.format_line(variable, value))
+            return label
+
+    class BuildDetailBox (gtk.EventBox):
+        def __init__(self, varlist = None, vallist = None, icon = None, color = HobColors.LIGHT_GRAY):
+            gtk.EventBox.__init__(self)
+
+            # set color
+            style = self.get_style().copy()
+            style.bg[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(color, False, False)
+            self.set_style(style)
+
+            self.hbox = gtk.HBox()
+            self.hbox.set_border_width(10)
+            self.add(self.hbox)
+
+            total_rows = 0
+            if varlist and vallist:
+                # pack the icon and the text on the left
+                total_rows += len(varlist)
+            self.table = gtk.Table(total_rows, 20, True)
+            self.table.set_row_spacings(6)
+            self.table.set_size_request(100, -1)
+            self.hbox.pack_start(self.table, expand=True, fill=True, padding=15)
+
+            colid = 0
+            rowid = 0
+            self.line_widgets = {}
+            if icon:
+                self.table.attach(icon, colid, colid + 2, 0, 1)
+                colid = colid + 2
+            if varlist and vallist:
+                for row in range(rowid, total_rows):
+                    index = row - rowid
+                    self.line_widgets[varlist[index]] = self.text2label(varlist[index], vallist[index])
+                    self.table.attach(self.line_widgets[varlist[index]], colid, 20, row, row + 1)
+                
+        def update_line_widgets(self, variable, value):
+            if len(self.line_widgets) == 0:
+                return
+            if not isinstance(self.line_widgets[variable], gtk.Label):
+                return
+            self.line_widgets[variable].set_markup(self.format_line(variable, value))
+
+        def wrap_line(self, inputs):
+            # wrap the long text of inputs
+            wrap_width_chars = 75
+            outputs = ""
+            tmps = inputs
+            less_chars = len(inputs)
+            while (less_chars - wrap_width_chars) > 0:
+                less_chars -= wrap_width_chars
+                outputs += tmps[:wrap_width_chars] + "\n               "
+                tmps = inputs[less_chars:]
+            outputs += tmps
+            return outputs
+
+        def format_line(self, variable, value):
+            wraped_value = self.wrap_line(value)
+            markup = "<span weight=\'bold\'>%s</span>" % variable
+            markup += "<span weight=\'normal\' foreground=\'#1c1c1c\' font_desc=\'14px\'>%s</span>" % wraped_value
+            return markup
+
+        def text2label(self, variable, value):
+            # append the name:value to the left box
+            # such as "Name: hob-core-minimal-variant-2011-12-15-beagleboard"
+            label = gtk.Label()
+            label.set_alignment(0.0, 0.5)
+            label.set_markup(self.format_line(variable, value))
+            return label
+
+    def __init__(self, builder):
+        super(ImageDetailsPage, self).__init__(builder, "Image details")
+
+        self.image_store = []
+        self.button_ids = {}
+        self.details_bottom_buttons = gtk.HBox(False, 6)
+        self.image_saved = False
+        self.create_visual_elements()
+        self.name_field_template = ""
+        self.description_field_template = ""
+
+    def create_visual_elements(self):
+        # create visual elements
+        # create the toolbar
+        self.toolbar = gtk.Toolbar()
+        self.toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL)
+        self.toolbar.set_style(gtk.TOOLBAR_BOTH)
+
+        my_images_button = self.append_toolbar_button(self.toolbar,
+            "Images",
+            hic.ICON_IMAGES_DISPLAY_FILE,
+            hic.ICON_IMAGES_HOVER_FILE,
+            "Open previously built images",
+            self.my_images_button_clicked_cb)
+        settings_button = self.append_toolbar_button(self.toolbar,
+            "Settings",
+            hic.ICON_SETTINGS_DISPLAY_FILE,
+            hic.ICON_SETTINGS_HOVER_FILE,
+            "View additional build settings",
+            self.settings_button_clicked_cb)
+
+        self.details_top_buttons = self.add_onto_top_bar(self.toolbar)
+
+    def _remove_all_widget(self):
+        children = self.get_children() or []
+        for child in children:
+            self.remove(child)
+        children = self.box_group_area.get_children() or []
+        for child in children:
+            self.box_group_area.remove(child)
+        children = self.details_bottom_buttons.get_children() or []
+        for child in children:
+            self.details_bottom_buttons.remove(child)
+
+    def show_page(self, step):
+        self.build_succeeded = (step == self.builder.IMAGE_GENERATED)
+        image_addr = self.builder.parameters.image_addr
+        image_names = self.builder.parameters.image_names
+        if self.build_succeeded:
+            machine = self.builder.configuration.curr_mach
+            base_image = self.builder.recipe_model.get_selected_image()
+            layers = self.builder.configuration.layers
+            pkg_num = "%s" % len(self.builder.package_model.get_selected_packages())
+            log_file = self.builder.current_logfile
+        else:
+            pkg_num = "N/A"
+            log_file = None
+
+        # remove
+        for button_id, button in self.button_ids.items():
+            button.disconnect(button_id)
+        self._remove_all_widget()
+
+        # repack
+        self.pack_start(self.details_top_buttons, expand=False, fill=False)
+        self.pack_start(self.group_align, expand=True, fill=True)
+
+        self.build_result = None
+        if self.image_saved or (self.build_succeeded and self.builder.current_step == self.builder.IMAGE_GENERATING):
+            # building is the previous step
+            icon = gtk.Image()
+            pixmap_path = hic.ICON_INDI_CONFIRM_FILE
+            color = HobColors.RUNNING
+            pix_buffer = gtk.gdk.pixbuf_new_from_file(pixmap_path)
+            icon.set_from_pixbuf(pix_buffer)
+            varlist = [""]
+            if self.image_saved:
+                vallist = ["Your image recipe has been saved"]
+            else:
+                vallist = ["Your image is ready"]
+            self.build_result = self.BuildDetailBox(varlist=varlist, vallist=vallist, icon=icon, color=color)
+            self.box_group_area.pack_start(self.build_result, expand=False, fill=False)
+
+        self.buttonlist = ["Build new image", "Save image recipe", "Run image", "Deploy image"]
+
+        # Name
+        self.image_store = []
+        self.toggled_image = ""
+        default_image_size = 0
+        self.num_toggled = 0
+        i = 0
+        for image_name in image_names:
+            image_size = HobPage._size_to_string(os.stat(os.path.join(image_addr, image_name)).st_size)
+
+            image_attr = ("run" if (self.test_type_runnable(image_name) and self.test_mach_runnable(image_name)) else \
+                          ("deploy" if self.test_deployable(image_name) else ""))
+            is_toggled = (image_attr != "")
+
+            if not self.toggled_image:
+                if i == (len(image_names) - 1):
+                    is_toggled = True
+                if is_toggled:
+                    default_image_size = image_size
+                    self.toggled_image = image_name
+
+            split_stuff = image_name.split('.')
+            if "rootfs" in split_stuff:
+                image_type = image_name[(len(split_stuff[0]) + len(".rootfs") + 1):]
+            else:
+                image_type = image_name[(len(split_stuff[0]) + 1):]
+
+            self.image_store.append({'name': image_name,
+                                    'type': image_type,
+                                    'size': image_size,
+                                    'is_toggled': is_toggled,
+                                    'action_attr': image_attr,})
+
+            i = i + 1
+            self.num_toggled += is_toggled
+
+        is_runnable = self.create_bottom_buttons(self.buttonlist, self.toggled_image)
+
+        # Generated image files info
+        varlist = ["Name: ", "Files created: ", "Directory: "]
+        vallist = []
+
+        vallist.append(image_name.split('.')[0])
+        vallist.append(',  '.join(fileitem['type'] for fileitem in self.image_store))
+        vallist.append(image_addr)
+
+        view_files_button = HobAltButton("View files")
+        view_files_button.connect("clicked", self.view_files_clicked_cb, image_addr)
+        view_files_button.set_tooltip_text("Open the directory containing the image files")
+        open_log_button = None
+        if log_file:
+            open_log_button = HobAltButton("Open log")
+            open_log_button.connect("clicked", self.open_log_clicked_cb, log_file)
+            open_log_button.set_tooltip_text("Open the build's log file")
+        self.image_detail = self.DetailBox(varlist=varlist, vallist=vallist, button=view_files_button, button2=open_log_button)
+        self.box_group_area.pack_start(self.image_detail, expand=False, fill=True)
+
+        # The default kernel box for the qemu images
+        self.sel_kernel = ""
+        self.kernel_detail = None
+        if 'qemu' in image_name:
+            self.sel_kernel = self.get_kernel_file_name()
+
+        #    varlist = ["Kernel: "]
+        #    vallist = []
+        #    vallist.append(self.sel_kernel)
+
+        #    change_kernel_button = HobAltButton("Change")
+        #    change_kernel_button.connect("clicked", self.change_kernel_cb)
+        #    change_kernel_button.set_tooltip_text("Change qemu kernel file")
+        #    self.kernel_detail = self.DetailBox(varlist=varlist, vallist=vallist, button=change_kernel_button)
+        #    self.box_group_area.pack_start(self.kernel_detail, expand=True, fill=True)
+
+        # Machine, Image recipe and Layers
+        layer_num_limit = 15
+        varlist = ["Machine: ", "Image recipe: ", "Layers: "]
+        vallist = []
+        self.setting_detail = None
+        if self.build_succeeded:
+            vallist.append(machine)
+            if self.builder.recipe_model.is_custom_image():
+                if self.builder.configuration.initial_selected_image == self.builder.recipe_model.__custom_image__:
+                    base_image ="New image recipe"
+                else:
+                    base_image = self.builder.configuration.initial_selected_image + " (edited)"
+            vallist.append(base_image)
+            i = 0
+            for layer in layers:
+                if i > layer_num_limit:
+                    break
+                varlist.append(" - ")
+                i += 1
+            vallist.append("")
+            i = 0
+            for layer in layers:
+                if i > layer_num_limit:
+                    break
+                elif i == layer_num_limit:
+                    vallist.append("and more...")
+                else:
+                    vallist.append(layer)
+                i += 1
+
+            edit_config_button = HobAltButton("Edit configuration")
+            edit_config_button.set_tooltip_text("Edit machine and image recipe")
+            edit_config_button.connect("clicked", self.edit_config_button_clicked_cb)
+            self.setting_detail = self.DetailBox(varlist=varlist, vallist=vallist, button=edit_config_button)
+            self.box_group_area.pack_start(self.setting_detail, expand=True, fill=True)
+
+        # Packages included, and Total image size
+        varlist = ["Packages included: ", "Total image size: "]
+        vallist = []
+        vallist.append(pkg_num)
+        vallist.append(default_image_size)
+        self.builder.configuration.image_size = default_image_size
+        self.builder.configuration.image_packages = self.builder.configuration.selected_packages
+        if self.build_succeeded:
+            edit_packages_button = HobAltButton("Edit packages")
+            edit_packages_button.set_tooltip_text("Edit the packages included in your image")
+            edit_packages_button.connect("clicked", self.edit_packages_button_clicked_cb)
+        else: # get to this page from "My images"
+            edit_packages_button = None
+        self.package_detail = self.DetailBox(varlist=varlist, vallist=vallist, button=edit_packages_button)
+        self.box_group_area.pack_start(self.package_detail, expand=True, fill=True)
+
+        # pack the buttons at the bottom, at this time they are already created.
+        if self.build_succeeded:
+            self.box_group_area.pack_end(self.details_bottom_buttons, expand=False, fill=False)
+        else: # for "My images" page
+            self.details_separator = gtk.HSeparator()
+            self.box_group_area.pack_start(self.details_separator, expand=False, fill=False)
+            self.box_group_area.pack_start(self.details_bottom_buttons, expand=False, fill=False)
+
+        self.show_all()
+        if self.kernel_detail and (not is_runnable):
+            self.kernel_detail.hide()
+        self.image_saved = False
+
+    def view_files_clicked_cb(self, button, image_addr):
+        subprocess.call("xdg-open /%s" % image_addr, shell=True)
+
+    def open_log_clicked_cb(self, button, log_file):
+        if log_file:
+            log_file = "file:///" + log_file
+            gtk.show_uri(screen=button.get_screen(), uri=log_file, timestamp=0)
+
+    def refresh_package_detail_box(self, image_size):
+        self.package_detail.update_line_widgets("Total image size: ", image_size)
+
+    def test_type_runnable(self, image_name):
+        type_runnable = False
+        for t in self.builder.parameters.runnable_image_types:
+            if image_name.endswith(t):
+                type_runnable = True
+                break
+        return type_runnable
+
+    def test_mach_runnable(self, image_name):
+        mach_runnable = False
+        for t in self.builder.parameters.runnable_machine_patterns:
+            if t in image_name:
+                mach_runnable = True
+                break
+        return mach_runnable
+
+    def test_deployable(self, image_name):
+        if self.builder.configuration.curr_mach.startswith("qemu"):
+            return False
+        deployable = False
+        for t in self.builder.parameters.deployable_image_types:
+            if image_name.endswith(t):
+                deployable = True
+                break
+        return deployable
+
+    def get_kernel_file_name(self, kernel_addr=""):
+        kernel_name = ""
+
+        if not kernel_addr:
+            kernel_addr = self.builder.parameters.image_addr
+
+        files = [f for f in os.listdir(kernel_addr) if f[0] <> '.']
+        for check_file in files:
+            if check_file.endswith(".bin"):
+                name_splits = check_file.split(".")[0]
+                if self.builder.parameters.kernel_image_type in name_splits.split("-"):
+                    kernel_name = check_file
+                    break
+
+        return kernel_name
+
+    def show_builded_images_dialog(self, widget, primary_action=""):
+        title = primary_action if primary_action else "Your builded images"
+        dialog = CrumbsDialog(title, self.builder,
+                              gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
+        dialog.set_border_width(12)
+
+        label = gtk.Label()
+        label.set_use_markup(True)
+        label.set_alignment(0.0, 0.5)
+        label.set_padding(12,0)
+        if primary_action == "Run image":
+            label.set_markup("<span font_desc='12'>Select the image file you want to run:</span>")
+        elif primary_action == "Deploy image":
+            label.set_markup("<span font_desc='12'>Select the image file you want to deploy:</span>")
+        else:
+            label.set_markup("<span font_desc='12'>Select the image file you want to %s</span>" % primary_action)
+        dialog.vbox.pack_start(label, expand=False, fill=False)
+
+        # filter created images as action attribution (deploy or run)
+        action_attr = ""
+        action_images = []
+        for fileitem in self.image_store:
+            action_attr = fileitem['action_attr']
+            if  (action_attr == 'run' and primary_action == "Run image") \
+             or (action_attr == 'deploy' and primary_action == "Deploy image"):
+                action_images.append(fileitem)
+
+        # pack the corresponding 'runnable' or 'deploy' radio_buttons, if there has no more than one file.
+        # assume that there does not both have 'deploy' and 'runnable' files in the same building result
+        # in possible as design.
+        curr_row = 0
+        rows = (len(action_images)) if len(action_images) < 10 else 10
+        table = gtk.Table(rows, 10, True)
+        table.set_row_spacings(6)
+        table.set_col_spacing(0, 12)
+        table.set_col_spacing(5, 12)
+
+        sel_parent_btn = None
+        for fileitem in action_images:
+            sel_btn = gtk.RadioButton(sel_parent_btn, fileitem['type'])
+            sel_parent_btn = sel_btn if not sel_parent_btn else sel_parent_btn
+            sel_btn.set_active(fileitem['is_toggled'])
+            sel_btn.connect('toggled', self.table_selected_cb, fileitem)
+            if curr_row < 10:
+                table.attach(sel_btn, 0, 4, curr_row, curr_row + 1, xpadding=24)
+            else:
+                table.attach(sel_btn, 5, 9, curr_row - 10, curr_row - 9, xpadding=24)
+            curr_row += 1
+
+        dialog.vbox.pack_start(table, expand=False, fill=False, padding=6)
+
+        button = dialog.add_button("Cancel", gtk.RESPONSE_CANCEL)
+        HobAltButton.style_button(button)
+
+        if primary_action:
+            button = dialog.add_button(primary_action, gtk.RESPONSE_YES)
+            HobButton.style_button(button)
+
+        dialog.show_all()
+
+        response = dialog.run()
+        dialog.destroy()
+
+        if response != gtk.RESPONSE_YES:
+            return
+
+        for fileitem in self.image_store:
+            if fileitem['is_toggled']:
+                if fileitem['action_attr'] == 'run':
+                    self.builder.runqemu_image(fileitem['name'], self.sel_kernel)
+                elif fileitem['action_attr'] == 'deploy':
+                    self.builder.deploy_image(fileitem['name'])
+
+    def table_selected_cb(self, tbutton, image):
+        image['is_toggled'] = tbutton.get_active()
+        if image['is_toggled']:
+            self.toggled_image = image['name']
+
+    def change_kernel_cb(self, widget):
+        kernel_path = self.builder.show_load_kernel_dialog()
+        if kernel_path and self.kernel_detail:
+            import os.path
+            self.sel_kernel = os.path.basename(kernel_path)
+            markup = self.kernel_detail.format_line("Kernel: ", self.sel_kernel)
+            label = ((self.kernel_detail.get_children()[0]).get_children()[0]).get_children()[0]
+            label.set_markup(markup)
+
+    def create_bottom_buttons(self, buttonlist, image_name):
+        # Create the buttons at the bottom
+        created = False
+        packed = False
+        self.button_ids = {}
+        is_runnable = False
+
+        # create button "Deploy image"
+        name = "Deploy image"
+        if name in buttonlist and self.test_deployable(image_name):
+            deploy_button = HobButton('Deploy image')
+            #deploy_button.set_size_request(205, 49)
+            deploy_button.set_tooltip_text("Burn a live image to a USB drive or flash memory")
+            deploy_button.set_flags(gtk.CAN_DEFAULT)
+            button_id = deploy_button.connect("clicked", self.deploy_button_clicked_cb)
+            self.button_ids[button_id] = deploy_button
+            self.details_bottom_buttons.pack_end(deploy_button, expand=False, fill=False)
+            created = True
+            packed = True
+
+        name = "Run image"
+        if name in buttonlist and self.test_type_runnable(image_name) and self.test_mach_runnable(image_name):
+            if created == True:
+                # separator
+                #label = gtk.Label(" or ")
+                #self.details_bottom_buttons.pack_end(label, expand=False, fill=False)
+
+                # create button "Run image"
+                run_button = HobAltButton("Run image")
+            else:
+                # create button "Run image" as the primary button
+                run_button = HobButton("Run image")
+                #run_button.set_size_request(205, 49)
+                run_button.set_flags(gtk.CAN_DEFAULT)
+                packed = True
+            run_button.set_tooltip_text("Start up an image with qemu emulator")
+            button_id = run_button.connect("clicked", self.run_button_clicked_cb)
+            self.button_ids[button_id] = run_button
+            self.details_bottom_buttons.pack_end(run_button, expand=False, fill=False)
+            created = True
+            is_runnable = True
+
+        name = "Save image recipe"
+        if name in buttonlist and self.builder.recipe_model.is_custom_image():
+            save_button = HobAltButton("Save image recipe")
+            save_button.set_tooltip_text("Keep your changes saving them as an image recipe")
+            save_button.set_sensitive(not self.image_saved)
+            button_id = save_button.connect("clicked", self.save_button_clicked_cb)
+            self.button_ids[button_id] = save_button
+            self.details_bottom_buttons.pack_end(save_button, expand=False, fill=False)
+
+        name = "Build new image"
+        if name in buttonlist:
+            # create button "Build new image"
+            if packed:
+                build_new_button = HobAltButton("Build new image")
+            else:
+                build_new_button = HobButton("Build new image")
+                build_new_button.set_flags(gtk.CAN_DEFAULT)
+            #build_new_button.set_size_request(205, 49)
+            self.details_bottom_buttons.pack_end(build_new_button, expand=False, fill=False)
+            build_new_button.set_tooltip_text("Create a new image from scratch")
+            button_id = build_new_button.connect("clicked", self.build_new_button_clicked_cb)
+            self.button_ids[button_id] = build_new_button
+
+        return is_runnable
+
+    def deploy_button_clicked_cb(self, button):
+        if self.toggled_image:
+            if self.num_toggled > 1:
+                self.set_sensitive(False)
+                self.show_builded_images_dialog(None, "Deploy image")
+                self.set_sensitive(True)
+            else:
+                self.builder.deploy_image(self.toggled_image)
+
+    def run_button_clicked_cb(self, button):
+        if self.toggled_image:
+            if self.num_toggled > 1:
+                self.set_sensitive(False)
+                self.show_builded_images_dialog(None, "Run image")
+                self.set_sensitive(True)
+            else:
+                self.builder.runqemu_image(self.toggled_image, self.sel_kernel)
+
+    def save_button_clicked_cb(self, button):
+        topdir = self.builder.get_topdir()
+        images_dir = topdir + "/recipes/images/custom/"
+        self.builder.ensure_dir(images_dir)
+
+        self.name_field_template = self.builder.image_configuration_page.custom_image_selected
+        if self.name_field_template:
+            image_path = self.builder.recipe_model.pn_path[self.name_field_template]
+            image_iter = self.builder.recipe_model.get_iter(image_path)
+            self.description_field_template = self.builder.recipe_model.get_value(image_iter, self.builder.recipe_model.COL_DESC)
+        else:
+            self.name_field_template = ""
+
+        dialog = SaveImageDialog(images_dir, self.name_field_template, self.description_field_template,
+                 "Save image recipe", self.builder, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
+        response = dialog.run()
+        dialog.destroy()
+
+    def build_new_button_clicked_cb(self, button):
+        self.builder.initiate_new_build_async()
+
+    def edit_config_button_clicked_cb(self, button):
+        self.builder.show_configuration()
+
+    def edit_packages_button_clicked_cb(self, button):
+        self.builder.show_packages()
+
+    def my_images_button_clicked_cb(self, button):
+        self.builder.show_load_my_images_dialog()
+
+    def settings_button_clicked_cb(self, button):
+        # Create an advanced settings dialog
+        response, settings_changed = self.builder.show_simple_settings_dialog()
+        if not response:
+            return
+        if settings_changed:
+            self.builder.reparse_post_adv_settings()
diff --git a/bitbake/lib/bb/ui/crumbs/packageselectionpage.py b/bitbake/lib/bb/ui/crumbs/packageselectionpage.py
new file mode 100755
index 0000000..7c62b36
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/packageselectionpage.py
@@ -0,0 +1,355 @@
+#!/usr/bin/env python
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2012        Intel Corporation
+#
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import glib
+from bb.ui.crumbs.hobcolor import HobColors
+from bb.ui.crumbs.hobwidget import HobViewTable, HobNotebook, HobAltButton, HobButton
+from bb.ui.crumbs.hoblistmodel import PackageListModel
+from bb.ui.crumbs.hobpages import HobPage
+
+#
+# PackageSelectionPage
+#
+class PackageSelectionPage (HobPage):
+
+    pages = [
+        {
+         'name'      : 'Included packages',
+         'tooltip'   : 'The packages currently included for your image',
+         'filter'    : { PackageListModel.COL_INC : [True] },
+         'search'    : 'Search packages by name',
+         'searchtip' : 'Enter a package name to find it',
+         'columns'   : [{
+                       'col_name' : 'Package name',
+                       'col_id'   : PackageListModel.COL_NAME,
+                       'col_style': 'text',
+                       'col_min'  : 100,
+                       'col_max'  : 300,
+                       'expand'   : 'True'
+                      }, {
+                       'col_name' : 'Size',
+                       'col_id'   : PackageListModel.COL_SIZE,
+                       'col_style': 'text',
+                       'col_min'  : 100,
+                       'col_max'  : 300,
+                       'expand'   : 'True'
+                      }, {
+                       'col_name' : 'Recipe',
+                       'col_id'   : PackageListModel.COL_RCP,
+                       'col_style': 'text',
+                       'col_min'  : 100,
+                       'col_max'  : 250,
+                       'expand'   : 'True'
+                      }, {
+                       'col_name' : 'Brought in by (+others)',
+                       'col_id'   : PackageListModel.COL_BINB,
+                       'col_style': 'binb',
+                       'col_min'  : 100,
+                       'col_max'  : 350,
+                       'expand'   : 'True'
+                      }, {
+                       'col_name' : 'Included',
+                       'col_id'   : PackageListModel.COL_INC,
+                       'col_style': 'check toggle',
+                       'col_min'  : 100,
+                       'col_max'  : 100
+                     }]
+        }, {
+         'name'      : 'All packages',
+         'tooltip'   : 'All packages that have been built',
+         'filter'    : {},
+         'search'    : 'Search packages by name',
+         'searchtip' : 'Enter a package name to find it',
+         'columns'   : [{
+                       'col_name' : 'Package name',
+                       'col_id'   : PackageListModel.COL_NAME,
+                       'col_style': 'text',
+                       'col_min'  : 100,
+                       'col_max'  : 400,
+                       'expand'   : 'True'
+                      }, {
+                       'col_name' : 'Size',
+                       'col_id'   : PackageListModel.COL_SIZE,
+                       'col_style': 'text',
+                       'col_min'  : 100,
+                       'col_max'  : 500,
+                       'expand'   : 'True'
+                      }, {
+                       'col_name' : 'Recipe',
+                       'col_id'   : PackageListModel.COL_RCP,
+                       'col_style': 'text',
+                       'col_min'  : 100,
+                       'col_max'  : 250,
+                       'expand'   : 'True'
+                      }, {
+                       'col_name' : 'Included',
+                       'col_id'   : PackageListModel.COL_INC,
+                       'col_style': 'check toggle',
+                       'col_min'  : 100,
+                       'col_max'  : 100
+                      }]
+        }
+    ]
+    
+    (INCLUDED,
+     ALL) = range(2)
+
+    def __init__(self, builder):
+        super(PackageSelectionPage, self).__init__(builder, "Edit packages")
+
+        # set invisible members
+        self.recipe_model = self.builder.recipe_model
+        self.package_model = self.builder.package_model
+
+        # create visual elements
+        self.create_visual_elements()
+
+    def included_clicked_cb(self, button):
+        self.ins.set_current_page(self.INCLUDED)
+
+    def create_visual_elements(self):
+        self.label = gtk.Label("Packages included: 0\nSelected packages size: 0 MB")
+        self.eventbox = self.add_onto_top_bar(self.label, 73)
+        self.pack_start(self.eventbox, expand=False, fill=False)
+        self.pack_start(self.group_align, expand=True, fill=True)
+
+        # set visible members
+        self.ins = HobNotebook()
+        self.tables = [] # we need to modify table when the dialog is shown
+
+        search_names = []
+        search_tips = []
+        # append the tab
+        for page in self.pages:
+            columns = page['columns']
+            name = page['name']
+            tab = HobViewTable(columns, name)
+            search_names.append(page['search'])
+            search_tips.append(page['searchtip'])
+            filter = page['filter']
+            sort_model = self.package_model.tree_model(filter, initial=True)
+            tab.set_model(sort_model)
+            tab.connect("toggled", self.table_toggled_cb, name)
+            tab.connect("button-release-event", self.button_click_cb)
+            tab.connect("cell-fadeinout-stopped", self.after_fadeout_checkin_include, filter)
+            self.ins.append_page(tab, page['name'], page['tooltip'])
+            self.tables.append(tab)
+
+        self.ins.set_entry(search_names, search_tips)
+        self.ins.search.connect("changed", self.search_entry_changed)
+
+        # add all into the dialog
+        self.box_group_area.pack_start(self.ins, expand=True, fill=True)
+
+        self.button_box = gtk.HBox(False, 6)
+        self.box_group_area.pack_start(self.button_box, expand=False, fill=False)
+
+        self.build_image_button = HobButton('Build image')
+        #self.build_image_button.set_size_request(205, 49)
+        self.build_image_button.set_tooltip_text("Build target image")
+        self.build_image_button.set_flags(gtk.CAN_DEFAULT)
+        self.build_image_button.grab_default()
+        self.build_image_button.connect("clicked", self.build_image_clicked_cb)
+        self.button_box.pack_end(self.build_image_button, expand=False, fill=False)
+
+        self.back_button = HobAltButton('Cancel')
+        self.back_button.connect("clicked", self.back_button_clicked_cb)
+        self.button_box.pack_end(self.back_button, expand=False, fill=False)
+
+    def search_entry_changed(self, entry):
+        text = entry.get_text()
+        if self.ins.search_focus:
+            self.ins.search_focus = False
+        elif self.ins.page_changed:
+            self.ins.page_change = False
+            self.filter_search(entry)
+        elif text not in self.ins.search_names:
+            self.filter_search(entry)
+
+    def filter_search(self, entry):
+        text = entry.get_text()
+        current_tab = self.ins.get_current_page()
+        filter = self.pages[current_tab]['filter']
+        filter[PackageListModel.COL_NAME] = text
+        self.tables[current_tab].set_model(self.package_model.tree_model(filter, search_data=text))
+        if self.package_model.filtered_nb == 0:
+            if not self.ins.get_nth_page(current_tab).top_bar:
+                self.ins.get_nth_page(current_tab).add_no_result_bar(entry)
+                self.ins.get_nth_page(current_tab).top_bar.set_no_show_all(True)
+            self.ins.get_nth_page(current_tab).top_bar.show()
+            self.ins.get_nth_page(current_tab).scroll.hide()
+        else:
+            if self.ins.get_nth_page(current_tab).top_bar:
+                self.ins.get_nth_page(current_tab).top_bar.hide()
+            self.ins.get_nth_page(current_tab).scroll.show()
+        if entry.get_text() == '':
+            entry.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False)
+        else:
+            entry.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, True)
+
+    def button_click_cb(self, widget, event):
+        path, col = widget.table_tree.get_cursor()
+        tree_model = widget.table_tree.get_model()
+        if path and col.get_title() != 'Included': # else activation is likely a removal
+            properties = {'binb': '' , 'name': '', 'size':'', 'recipe':'', 'files_list':''}
+            properties['binb'] = tree_model.get_value(tree_model.get_iter(path), PackageListModel.COL_BINB)
+            properties['name'] = tree_model.get_value(tree_model.get_iter(path), PackageListModel.COL_NAME)
+            properties['size'] = tree_model.get_value(tree_model.get_iter(path), PackageListModel.COL_SIZE)
+            properties['recipe'] = tree_model.get_value(tree_model.get_iter(path), PackageListModel.COL_RCP)
+            properties['files_list'] = tree_model.get_value(tree_model.get_iter(path), PackageListModel.COL_FLIST)
+
+            self.builder.show_recipe_property_dialog(properties)
+
+    def open_log_clicked_cb(self, button, log_file):
+        if log_file:
+            log_file = "file:///" + log_file
+            gtk.show_uri(screen=button.get_screen(), uri=log_file, timestamp=0)
+
+    def show_page(self, log_file):
+        children = self.button_box.get_children() or []
+        for child in children:
+            self.button_box.remove(child)
+        # re-packed the buttons as request, add the 'open log' button if build success
+        self.button_box.pack_end(self.build_image_button, expand=False, fill=False)
+        if log_file:
+            open_log_button = HobAltButton("Open log")
+            open_log_button.connect("clicked", self.open_log_clicked_cb, log_file)
+            open_log_button.set_tooltip_text("Open the build's log file")
+            self.button_box.pack_end(open_log_button, expand=False, fill=False)
+        self.button_box.pack_end(self.back_button, expand=False, fill=False)
+        self.show_all()
+
+    def build_image_clicked_cb(self, button):
+        self.builder.parsing_warnings = []
+        self.builder.build_image()
+
+    def refresh_tables(self):
+        self.ins.reset_entry(self.ins.search, 0)
+        for tab in self.tables:
+            index = self.tables.index(tab)
+            filter = self.pages[index]['filter']
+            tab.set_model(self.package_model.tree_model(filter, initial=True))
+
+    def back_button_clicked_cb(self, button):
+        if self.builder.previous_step ==  self.builder.IMAGE_GENERATED:
+            self.builder.restore_initial_selected_packages()
+            self.refresh_selection()
+            self.builder.show_image_details()
+        else:
+            self.builder.show_configuration()
+        self.refresh_tables()
+
+    def refresh_selection(self):
+        self.builder.configuration.selected_packages = self.package_model.get_selected_packages()
+        self.builder.configuration.user_selected_packages = self.package_model.get_user_selected_packages()
+        selected_packages_num = len(self.builder.configuration.selected_packages)
+        selected_packages_size = self.package_model.get_packages_size()
+        selected_packages_size_str = HobPage._size_to_string(selected_packages_size)
+
+        if self.builder.configuration.image_packages == self.builder.configuration.selected_packages:
+            image_total_size_str = self.builder.configuration.image_size
+        else:
+            image_overhead_factor = self.builder.configuration.image_overhead_factor
+            image_rootfs_size = self.builder.configuration.image_rootfs_size / 1024 # image_rootfs_size is KB
+            image_extra_size = self.builder.configuration.image_extra_size / 1024 # image_extra_size is KB
+            base_size = image_overhead_factor * selected_packages_size
+            image_total_size = max(base_size, image_rootfs_size) + image_extra_size
+            if "zypper" in self.builder.configuration.selected_packages:
+                image_total_size += (51200 * 1024)
+            image_total_size_str = HobPage._size_to_string(image_total_size)
+
+        self.label.set_label("Packages included: %s\nSelected packages size: %s\nEstimated image size: %s" %
+                            (selected_packages_num, selected_packages_size_str, image_total_size_str))
+        self.ins.show_indicator_icon("Included packages", selected_packages_num)
+
+    def toggle_item_idle_cb(self, path, view_tree, cell, pagename):
+        if not self.package_model.path_included(path):
+            self.package_model.include_item(item_path=path, binb="User Selected")
+        else:
+            self.pre_fadeout_checkout_include(view_tree)
+            self.package_model.exclude_item(item_path=path)
+            self.render_fadeout(view_tree, cell)
+
+        self.refresh_selection()
+        if not self.builder.customized:
+            self.builder.customized = True
+            self.builder.set_base_image()
+            self.builder.configuration.selected_image = self.recipe_model.__custom_image__
+            self.builder.rcppkglist_populated()
+
+        self.builder.window_sensitive(True)
+        view_model = view_tree.get_model()
+        vpath = self.package_model.convert_path_to_vpath(view_model, path)
+        view_tree.set_cursor(vpath)
+
+    def table_toggled_cb(self, table, cell, view_path, toggled_columnid, view_tree, pagename):
+        # Click to include a package
+        self.builder.window_sensitive(False)
+        view_model = view_tree.get_model()
+        path = self.package_model.convert_vpath_to_path(view_model, view_path)
+        glib.idle_add(self.toggle_item_idle_cb, path, view_tree, cell, pagename)
+
+    def pre_fadeout_checkout_include(self, tree):
+        #after the fadeout the table will be sorted as before
+        self.sort_column_id = self.package_model.sort_column_id
+        self.sort_order = self.package_model.sort_order
+
+        self.package_model.resync_fadeout_column(self.package_model.get_iter_first())
+        # Check out a model which base on the column COL_FADE_INC,
+        # it's save the prev state of column COL_INC before do exclude_item
+        filter = { PackageListModel.COL_FADE_INC  : [True]}
+        new_model = self.package_model.tree_model(filter, excluded_items_ahead=True)
+        tree.set_model(new_model)
+        tree.expand_all()
+
+    def get_excluded_rows(self, to_render_cells, model, it):
+        while it:
+            path = model.get_path(it)
+            prev_cell_is_active = model.get_value(it, PackageListModel.COL_FADE_INC)
+            curr_cell_is_active = model.get_value(it, PackageListModel.COL_INC)
+            if (prev_cell_is_active == True) and (curr_cell_is_active == False):
+                to_render_cells.append(path)
+            if model.iter_has_child(it):
+                self.get_excluded_rows(to_render_cells, model, model.iter_children(it))
+            it = model.iter_next(it)
+
+        return to_render_cells
+
+    def render_fadeout(self, tree, cell):
+        if (not cell) or (not tree):
+            return
+        to_render_cells = []
+        view_model = tree.get_model()
+        self.get_excluded_rows(to_render_cells, view_model, view_model.get_iter_first())
+
+        cell.fadeout(tree, 1000, to_render_cells)
+
+    def after_fadeout_checkin_include(self, table, ctrl, cell, tree, filter):
+        self.package_model.sort_column_id = self.sort_column_id
+        self.package_model.sort_order = self.sort_order
+        tree.set_model(self.package_model.tree_model(filter))
+        tree.expand_all()
+
+    def set_packages_curr_tab(self, curr_page):
+        self.ins.set_current_page(curr_page)
+
diff --git a/bitbake/lib/bb/ui/crumbs/persistenttooltip.py b/bitbake/lib/bb/ui/crumbs/persistenttooltip.py
new file mode 100644
index 0000000..927c194
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/persistenttooltip.py
@@ -0,0 +1,186 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2012   Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gobject
+import gtk
+try:
+    import gconf
+except:
+    pass
+
+class PersistentTooltip(gtk.Window):
+    """
+    A tooltip which persists once shown until the user dismisses it with the Esc
+    key or by clicking the close button.
+
+    # FIXME: the PersistentTooltip should be disabled when the user clicks anywhere off
+    # it. We can't do this with focus-out-event becuase modal ensures we have focus?
+
+    markup: some Pango text markup to display in the tooltip
+    """
+    def __init__(self, markup, parent_win=None):
+        gtk.Window.__init__(self, gtk.WINDOW_POPUP)
+
+        # Inherit the system theme for a tooltip
+        style = gtk.rc_get_style_by_paths(gtk.settings_get_default(),
+                        'gtk-tooltip', 'gtk-tooltip', gobject.TYPE_NONE)
+        self.set_style(style)
+
+        # The placement of the close button on the tip should reflect how the
+        # window manager of the users system places close buttons. Try to read
+        # the metacity gconf key to determine whether the close button is on the
+        # left or the right.
+        # In the case that we can't determine the users configuration we default
+        # to close buttons being on the right.
+        __button_right = True
+        try:
+            client = gconf.client_get_default()
+            order = client.get_string("/apps/metacity/general/button_layout")
+            if order and order.endswith(":"):
+                __button_right = False
+        except NameError:
+            pass
+
+        # We need to ensure we're only shown once
+        self.shown = False
+
+        # We don't want any WM decorations
+        self.set_decorated(False)
+        # We don't want to show in the taskbar or window switcher
+        self.set_skip_pager_hint(True)
+        self.set_skip_taskbar_hint(True)
+        # We must be modal to ensure we grab focus when presented from a gtk.Dialog
+        self.set_modal(True)
+
+        self.set_border_width(0)
+        self.set_position(gtk.WIN_POS_MOUSE)
+        self.set_opacity(0.95)
+
+        # Ensure a reasonable minimum size
+        self.set_geometry_hints(self, 100, 50)
+
+        # Set this window as a transient window for parent(main window)
+        if parent_win:
+            self.set_transient_for(parent_win)
+            self.set_destroy_with_parent(True)
+        # Draw our label and close buttons
+        hbox = gtk.HBox(False, 0)
+        hbox.show()
+        self.add(hbox)
+
+        img = gtk.Image()
+        img.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_BUTTON)
+
+        self.button = gtk.Button()
+        self.button.set_image(img)
+        self.button.connect("clicked", self._dismiss_cb)
+        self.button.set_flags(gtk.CAN_DEFAULT)
+        self.button.grab_focus()
+        self.button.show()
+        vbox = gtk.VBox(False, 0)
+        vbox.show()
+        vbox.pack_start(self.button, False, False, 0)
+        if __button_right:
+            hbox.pack_end(vbox, True, True, 0)
+        else:
+            hbox.pack_start(vbox, True, True, 0)
+
+        self.set_default(self.button)
+
+        bin = gtk.HBox(True, 6)
+        bin.set_border_width(6)
+        bin.show()
+        self.label = gtk.Label()
+        self.label.set_line_wrap(True)
+        # We want to match the colours of the normal tooltips, as dictated by
+        # the users gtk+-2.0 theme, wherever possible - on some systems this
+        # requires explicitly setting a fg_color for the label which matches the
+        # tooltip_fg_color
+        settings = gtk.settings_get_default()
+        colours = settings.get_property('gtk-color-scheme').split('\n')
+        # remove any empty lines, there's likely to be a trailing one after
+        # calling split on a dictionary-like string
+        colours = filter(None, colours)
+        for col in colours:
+            item, val = col.split(': ')
+            if item == 'tooltip_fg_color':
+                style = self.label.get_style()
+                style.fg[gtk.STATE_NORMAL] = gtk.gdk.color_parse(val)
+                self.label.set_style(style)
+                break # we only care for the tooltip_fg_color
+
+        self.label.set_markup(markup)
+        self.label.show()
+        bin.add(self.label)
+        hbox.pack_end(bin, True, True, 6)
+
+        # add the original URL display for user reference
+        if 'a href' in markup:
+            hbox.set_tooltip_text(self.get_markup_url(markup))
+        hbox.show()
+
+        self.connect("key-press-event", self._catch_esc_cb)
+
+    """
+    Callback when the PersistentTooltip's close button is clicked.
+    Hides the PersistentTooltip.
+    """
+    def _dismiss_cb(self, button):
+        self.hide()
+        return True
+
+    """
+    Callback when the Esc key is detected. Hides the PersistentTooltip.
+    """
+    def _catch_esc_cb(self, widget, event):
+        keyname = gtk.gdk.keyval_name(event.keyval)
+        if keyname == "Escape":
+            self.hide()
+        return True
+
+    """
+    Called to present the PersistentTooltip.
+    Overrides the superclasses show() method to include state tracking.
+    """
+    def show(self):
+        if not self.shown:
+            self.shown = True
+            gtk.Window.show(self)
+
+    """
+    Called to hide the PersistentTooltip.
+    Overrides the superclasses hide() method to include state tracking.
+    """
+    def hide(self):
+        self.shown = False
+        gtk.Window.hide(self)
+
+    """
+    Called to get the hyperlink URL from markup text.
+    """
+    def get_markup_url(self, markup):
+        url = "http:"
+        if markup and type(markup) == str:
+            s = markup
+            if 'http:' in s:
+                import re
+                url = re.search('(http:[^,\\ "]+)', s).group(0)
+
+        return url
diff --git a/bitbake/lib/bb/ui/crumbs/progress.py b/bitbake/lib/bb/ui/crumbs/progress.py
new file mode 100644
index 0000000..1d28a11
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/progress.py
@@ -0,0 +1,23 @@
+import gtk
+
+class ProgressBar(gtk.Dialog):
+    def __init__(self, parent):
+
+        gtk.Dialog.__init__(self, flags=(gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT))
+        self.set_title("Parsing metadata, please wait...")
+        self.set_default_size(500, 0)
+        self.set_transient_for(parent)
+        self.progress = gtk.ProgressBar()
+        self.vbox.pack_start(self.progress)
+        self.show_all()
+
+    def set_text(self, msg):
+        self.progress.set_text(msg)
+
+    def update(self, x, y):
+        self.progress.set_fraction(float(x)/float(y))
+        self.progress.set_text("%2d %%" % (x*100/y))
+
+    def pulse(self):
+        self.progress.set_text("Loading...")
+        self.progress.pulse()
diff --git a/bitbake/lib/bb/ui/crumbs/progressbar.py b/bitbake/lib/bb/ui/crumbs/progressbar.py
new file mode 100644
index 0000000..3e2c660
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/progressbar.py
@@ -0,0 +1,59 @@
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011        Intel Corporation
+#
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+from bb.ui.crumbs.hobcolor import HobColors
+
+class HobProgressBar (gtk.ProgressBar):
+    def __init__(self):
+        gtk.ProgressBar.__init__(self)
+        self.set_rcstyle(True)
+        self.percentage = 0
+
+    def set_rcstyle(self, status):
+        rcstyle = gtk.RcStyle()
+        rcstyle.fg[2] = gtk.gdk.Color(HobColors.BLACK)
+        if status == "stop":
+            rcstyle.bg[3] = gtk.gdk.Color(HobColors.WARNING)
+        elif status == "fail":
+            rcstyle.bg[3] = gtk.gdk.Color(HobColors.ERROR)
+        else:
+            rcstyle.bg[3] = gtk.gdk.Color(HobColors.RUNNING)
+        self.modify_style(rcstyle)
+
+    def set_title(self, text=None):
+        if not text:
+            text = ""
+        text += " %.0f%%" % self.percentage
+        self.set_text(text)
+
+    def set_stop_title(self, text=None):
+	if not text:
+	    text = ""
+	self.set_text(text)
+
+    def reset(self):
+        self.set_fraction(0)
+        self.set_text("")
+        self.set_rcstyle(True)
+        self.percentage = 0
+
+    def update(self, fraction):
+        self.percentage = int(fraction * 100)
+        self.set_fraction(fraction)
diff --git a/bitbake/lib/bb/ui/crumbs/puccho.glade b/bitbake/lib/bb/ui/crumbs/puccho.glade
new file mode 100644
index 0000000..d7553a6
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/puccho.glade
@@ -0,0 +1,606 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--Generated with glade3 3.4.5 on Mon Nov 10 12:24:12 2008 -->
+<glade-interface>
+  <widget class="GtkDialog" id="build_dialog">
+    <property name="title" translatable="yes">Start a build</property>
+    <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkTable" id="build_table">
+            <property name="visible">True</property>
+            <property name="border_width">6</property>
+            <property name="n_rows">7</property>
+            <property name="n_columns">3</property>
+            <property name="column_spacing">5</property>
+            <property name="row_spacing">6</property>
+            <child>
+              <widget class="GtkAlignment" id="status_alignment">
+                <property name="visible">True</property>
+                <property name="left_padding">12</property>
+                <child>
+                  <widget class="GtkHBox" id="status_hbox">
+                    <property name="spacing">6</property>
+                    <child>
+                      <widget class="GtkImage" id="status_image">
+                        <property name="visible">True</property>
+                        <property name="no_show_all">True</property>
+                        <property name="xalign">0</property>
+                        <property name="stock">gtk-dialog-error</property>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkLabel" id="status_label">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">If you see this text something is wrong...</property>
+                        <property name="use_markup">True</property>
+                        <property name="use_underline">True</property>
+                      </widget>
+                      <packing>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </widget>
+                </child>
+              </widget>
+              <packing>
+                <property name="right_attach">3</property>
+                <property name="top_attach">2</property>
+                <property name="bottom_attach">3</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="label2">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">&lt;b&gt;Build configuration&lt;/b&gt;</property>
+                <property name="use_markup">True</property>
+              </widget>
+              <packing>
+                <property name="right_attach">3</property>
+                <property name="top_attach">3</property>
+                <property name="bottom_attach">4</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkComboBox" id="image_combo">
+                <property name="visible">True</property>
+                <property name="sensitive">False</property>
+              </widget>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">2</property>
+                <property name="top_attach">6</property>
+                <property name="bottom_attach">7</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="image_label">
+                <property name="visible">True</property>
+                <property name="sensitive">False</property>
+                <property name="xalign">0</property>
+                <property name="xpad">12</property>
+                <property name="label" translatable="yes">Image:</property>
+              </widget>
+              <packing>
+                <property name="top_attach">6</property>
+                <property name="bottom_attach">7</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkComboBox" id="distribution_combo">
+                <property name="visible">True</property>
+                <property name="sensitive">False</property>
+              </widget>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">2</property>
+                <property name="top_attach">5</property>
+                <property name="bottom_attach">6</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="distribution_label">
+                <property name="visible">True</property>
+                <property name="sensitive">False</property>
+                <property name="xalign">0</property>
+                <property name="xpad">12</property>
+                <property name="label" translatable="yes">Distribution:</property>
+              </widget>
+              <packing>
+                <property name="top_attach">5</property>
+                <property name="bottom_attach">6</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkComboBox" id="machine_combo">
+                <property name="visible">True</property>
+                <property name="sensitive">False</property>
+              </widget>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">2</property>
+                <property name="top_attach">4</property>
+                <property name="bottom_attach">5</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="machine_label">
+                <property name="visible">True</property>
+                <property name="sensitive">False</property>
+                <property name="xalign">0</property>
+                <property name="xpad">12</property>
+                <property name="label" translatable="yes">Machine:</property>
+              </widget>
+              <packing>
+                <property name="top_attach">4</property>
+                <property name="bottom_attach">5</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkButton" id="refresh_button">
+                <property name="visible">True</property>
+                <property name="sensitive">False</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="label" translatable="yes">gtk-refresh</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">0</property>
+              </widget>
+              <packing>
+                <property name="left_attach">2</property>
+                <property name="right_attach">3</property>
+                <property name="top_attach">1</property>
+                <property name="bottom_attach">2</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkEntry" id="location_entry">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="width_chars">32</property>
+              </widget>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">2</property>
+                <property name="top_attach">1</property>
+                <property name="bottom_attach">2</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="label3">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="xpad">12</property>
+                <property name="label" translatable="yes">Location:</property>
+              </widget>
+              <packing>
+                <property name="top_attach">1</property>
+                <property name="bottom_attach">2</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="label1">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">&lt;b&gt;Repository&lt;/b&gt;</property>
+                <property name="use_markup">True</property>
+              </widget>
+              <packing>
+                <property name="right_attach">3</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkAlignment" id="alignment1">
+                <property name="visible">True</property>
+                <child>
+                  <placeholder/>
+                </child>
+              </widget>
+              <packing>
+                <property name="left_attach">2</property>
+                <property name="right_attach">3</property>
+                <property name="top_attach">4</property>
+                <property name="bottom_attach">5</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkAlignment" id="alignment2">
+                <property name="visible">True</property>
+                <child>
+                  <placeholder/>
+                </child>
+              </widget>
+              <packing>
+                <property name="left_attach">2</property>
+                <property name="right_attach">3</property>
+                <property name="top_attach">5</property>
+                <property name="bottom_attach">6</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkAlignment" id="alignment3">
+                <property name="visible">True</property>
+                <child>
+                  <placeholder/>
+                </child>
+              </widget>
+              <packing>
+                <property name="left_attach">2</property>
+                <property name="right_attach">3</property>
+                <property name="top_attach">6</property>
+                <property name="bottom_attach">7</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="layout_style">GTK_BUTTONBOX_END</property>
+            <child>
+              <placeholder/>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+  <widget class="GtkDialog" id="dialog2">
+    <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox2">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkTable" id="table2">
+            <property name="visible">True</property>
+            <property name="border_width">6</property>
+            <property name="n_rows">7</property>
+            <property name="n_columns">3</property>
+            <property name="column_spacing">6</property>
+            <property name="row_spacing">6</property>
+            <child>
+              <widget class="GtkLabel" id="label7">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">&lt;b&gt;Repositories&lt;/b&gt;</property>
+                <property name="use_markup">True</property>
+              </widget>
+              <packing>
+                <property name="right_attach">3</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkAlignment" id="alignment4">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="left_padding">12</property>
+                <child>
+                  <widget class="GtkScrolledWindow" id="scrolledwindow1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <child>
+                      <widget class="GtkTreeView" id="treeview1">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="headers_clickable">True</property>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+              </widget>
+              <packing>
+                <property name="right_attach">3</property>
+                <property name="top_attach">2</property>
+                <property name="bottom_attach">3</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkEntry" id="entry1">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+              </widget>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">3</property>
+                <property name="top_attach">1</property>
+                <property name="bottom_attach">2</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="label9">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">&lt;b&gt;Additional packages&lt;/b&gt;</property>
+                <property name="use_markup">True</property>
+              </widget>
+              <packing>
+                <property name="right_attach">3</property>
+                <property name="top_attach">4</property>
+                <property name="bottom_attach">5</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkAlignment" id="alignment6">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="xscale">0</property>
+                <child>
+                  <widget class="GtkLabel" id="label8">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="yalign">0</property>
+                    <property name="xpad">12</property>
+                    <property name="label" translatable="yes">Location: </property>
+                  </widget>
+                </child>
+              </widget>
+              <packing>
+                <property name="top_attach">1</property>
+                <property name="bottom_attach">2</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkAlignment" id="alignment7">
+                <property name="visible">True</property>
+                <property name="xalign">1</property>
+                <property name="xscale">0</property>
+                <child>
+                  <widget class="GtkHButtonBox" id="hbuttonbox1">
+                    <property name="visible">True</property>
+                    <property name="spacing">5</property>
+                    <child>
+                      <widget class="GtkButton" id="button7">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="label" translatable="yes">gtk-remove</property>
+                        <property name="use_stock">True</property>
+                        <property name="response_id">0</property>
+                      </widget>
+                    </child>
+                    <child>
+                      <widget class="GtkButton" id="button6">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="label" translatable="yes">gtk-edit</property>
+                        <property name="use_stock">True</property>
+                        <property name="response_id">0</property>
+                      </widget>
+                      <packing>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkButton" id="button5">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="label" translatable="yes">gtk-add</property>
+                        <property name="use_stock">True</property>
+                        <property name="response_id">0</property>
+                      </widget>
+                      <packing>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                  </widget>
+                </child>
+              </widget>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">3</property>
+                <property name="top_attach">3</property>
+                <property name="bottom_attach">4</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkAlignment" id="alignment5">
+                <property name="visible">True</property>
+                <child>
+                  <placeholder/>
+                </child>
+              </widget>
+              <packing>
+                <property name="top_attach">3</property>
+                <property name="bottom_attach">4</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="label10">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="yalign">0</property>
+                <property name="xpad">12</property>
+                <property name="label" translatable="yes">Search:</property>
+              </widget>
+              <packing>
+                <property name="top_attach">5</property>
+                <property name="bottom_attach">6</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkEntry" id="entry2">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+              </widget>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">3</property>
+                <property name="top_attach">5</property>
+                <property name="bottom_attach">6</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkAlignment" id="alignment8">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="left_padding">12</property>
+                <child>
+                  <widget class="GtkScrolledWindow" id="scrolledwindow2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                    <child>
+                      <widget class="GtkTreeView" id="treeview2">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="headers_clickable">True</property>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+              </widget>
+              <packing>
+                <property name="right_attach">3</property>
+                <property name="top_attach">6</property>
+                <property name="bottom_attach">7</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area2">
+            <property name="visible">True</property>
+            <property name="layout_style">GTK_BUTTONBOX_END</property>
+            <child>
+              <widget class="GtkButton" id="button4">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="label" translatable="yes">gtk-close</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">0</property>
+              </widget>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+  <widget class="GtkWindow" id="main_window">
+    <child>
+      <widget class="GtkVBox" id="main_window_vbox">
+        <property name="visible">True</property>
+        <child>
+          <widget class="GtkToolbar" id="main_toolbar">
+            <property name="visible">True</property>
+            <child>
+              <widget class="GtkToolButton" id="main_toolbutton_build">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">Build</property>
+                <property name="stock_id">gtk-execute</property>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+          </packing>
+        </child>
+        <child>
+          <widget class="GtkVPaned" id="vpaned1">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <child>
+              <widget class="GtkScrolledWindow" id="results_scrolledwindow">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                <child>
+                  <placeholder/>
+                </child>
+              </widget>
+              <packing>
+                <property name="resize">False</property>
+                <property name="shrink">True</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkScrolledWindow" id="progress_scrolledwindow">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                <child>
+                  <placeholder/>
+                </child>
+              </widget>
+              <packing>
+                <property name="resize">True</property>
+                <property name="shrink">True</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+</glade-interface>
diff --git a/bitbake/lib/bb/ui/crumbs/recipeselectionpage.py b/bitbake/lib/bb/ui/crumbs/recipeselectionpage.py
new file mode 100755
index 0000000..58db43f
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/recipeselectionpage.py
@@ -0,0 +1,335 @@
+#!/usr/bin/env python
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2012        Intel Corporation
+#
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+# Authored by Shane Wang <shane.wang@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import glib
+from bb.ui.crumbs.hobcolor import HobColors
+from bb.ui.crumbs.hobwidget import HobViewTable, HobNotebook, HobAltButton, HobButton
+from bb.ui.crumbs.hoblistmodel import RecipeListModel
+from bb.ui.crumbs.hobpages import HobPage
+
+#
+# RecipeSelectionPage
+#
+class RecipeSelectionPage (HobPage):
+    pages = [
+        {
+         'name'      : 'Included recipes',
+         'tooltip'   : 'The recipes currently included for your image',
+         'filter'    : { RecipeListModel.COL_INC  : [True],
+                       RecipeListModel.COL_TYPE : ['recipe', 'packagegroup'] },
+         'search'    : 'Search recipes by name',
+         'searchtip' : 'Enter a recipe name to find it',
+         'columns'   : [{
+                       'col_name' : 'Recipe name',
+                       'col_id'   : RecipeListModel.COL_NAME,
+                       'col_style': 'text',
+                       'col_min'  : 100,
+                       'col_max'  : 400,
+                       'expand'   : 'True'
+                      }, {
+                       'col_name' : 'Group',
+                       'col_id'   : RecipeListModel.COL_GROUP,
+                       'col_style': 'text',
+                       'col_min'  : 100,
+                       'col_max'  : 300,
+                       'expand'   : 'True'
+                      }, {
+                       'col_name' : 'Brought in by (+others)',
+                       'col_id'   : RecipeListModel.COL_BINB,
+                       'col_style': 'binb',
+                       'col_min'  : 100,
+                       'col_max'  : 500,
+                       'expand'   : 'True'
+                      }, {
+                       'col_name' : 'Included',
+                       'col_id'   : RecipeListModel.COL_INC,
+                       'col_style': 'check toggle',
+                       'col_min'  : 100,
+                       'col_max'  : 100
+                      }]
+        }, {
+         'name'      : 'All recipes',
+         'tooltip'   : 'All recipes in your configured layers',
+         'filter'    : { RecipeListModel.COL_TYPE : ['recipe'] },
+         'search'    : 'Search recipes by name',
+         'searchtip' : 'Enter a recipe name to find it',
+         'columns'   : [{
+                       'col_name' : 'Recipe name',
+                       'col_id'   : RecipeListModel.COL_NAME,
+                       'col_style': 'text',
+                       'col_min'  : 100,
+                       'col_max'  : 400,
+                       'expand'   : 'True'
+                      }, {
+                       'col_name' : 'Group',
+                       'col_id'   : RecipeListModel.COL_GROUP,
+                       'col_style': 'text',
+                       'col_min'  : 100,
+                       'col_max'  : 400,
+                       'expand'   : 'True'
+                      }, {
+                       'col_name' : 'License',
+                       'col_id'   : RecipeListModel.COL_LIC,
+                       'col_style': 'text',
+                       'col_min'  : 100,
+                       'col_max'  : 400,
+                       'expand'   : 'True'
+                      }, {
+                       'col_name' : 'Included',
+                       'col_id'   : RecipeListModel.COL_INC,
+                       'col_style': 'check toggle',
+                       'col_min'  : 100,
+                       'col_max'  : 100
+                      }]
+        }, {
+         'name'      : 'Package Groups',
+         'tooltip'   : 'All package groups in your configured layers',
+         'filter'    : { RecipeListModel.COL_TYPE : ['packagegroup'] },
+         'search'    : 'Search package groups by name',
+         'searchtip' : 'Enter a package group name to find it',
+         'columns'   : [{
+                       'col_name' : 'Package group name',
+                       'col_id'   : RecipeListModel.COL_NAME,
+                       'col_style': 'text',
+                       'col_min'  : 100,
+                       'col_max'  : 400,
+                       'expand'   : 'True'
+                      }, {
+                       'col_name' : 'Included',
+                       'col_id'   : RecipeListModel.COL_INC,
+                       'col_style': 'check toggle',
+                       'col_min'  : 100,
+                       'col_max'  : 100
+                      }]
+        }
+    ]
+    
+    (INCLUDED,
+     ALL,
+     TASKS) = range(3)
+
+    def __init__(self, builder = None):
+        super(RecipeSelectionPage, self).__init__(builder, "Step 1 of 2: Edit recipes")
+
+        # set invisible members
+        self.recipe_model = self.builder.recipe_model
+
+        # create visual elements
+        self.create_visual_elements()
+
+    def included_clicked_cb(self, button):
+        self.ins.set_current_page(self.INCLUDED)
+
+    def create_visual_elements(self):
+        self.eventbox = self.add_onto_top_bar(None, 73)
+        self.pack_start(self.eventbox, expand=False, fill=False)
+        self.pack_start(self.group_align, expand=True, fill=True)
+
+        # set visible members
+        self.ins = HobNotebook()
+        self.tables = [] # we need modify table when the dialog is shown
+
+        search_names = []
+        search_tips = []
+        # append the tabs in order
+        for page in self.pages:
+            columns = page['columns']
+            name = page['name']
+            tab = HobViewTable(columns, name)
+            search_names.append(page['search'])
+            search_tips.append(page['searchtip'])
+            filter = page['filter']
+            sort_model = self.recipe_model.tree_model(filter, initial=True)
+            tab.set_model(sort_model)
+            tab.connect("toggled", self.table_toggled_cb, name)
+            tab.connect("button-release-event", self.button_click_cb)
+            tab.connect("cell-fadeinout-stopped", self.after_fadeout_checkin_include, filter)
+            self.ins.append_page(tab, page['name'], page['tooltip'])
+            self.tables.append(tab)
+
+        self.ins.set_entry(search_names, search_tips)
+        self.ins.search.connect("changed", self.search_entry_changed)
+
+        # add all into the window
+        self.box_group_area.pack_start(self.ins, expand=True, fill=True)
+
+        button_box = gtk.HBox(False, 6)
+        self.box_group_area.pack_end(button_box, expand=False, fill=False)
+
+        self.build_packages_button = HobButton('Build packages')
+        #self.build_packages_button.set_size_request(205, 49)
+        self.build_packages_button.set_tooltip_text("Build selected recipes into packages")
+        self.build_packages_button.set_flags(gtk.CAN_DEFAULT)
+        self.build_packages_button.grab_default()
+        self.build_packages_button.connect("clicked", self.build_packages_clicked_cb)
+        button_box.pack_end(self.build_packages_button, expand=False, fill=False)
+
+        self.back_button = HobAltButton('Cancel')
+        self.back_button.connect("clicked", self.back_button_clicked_cb)
+        button_box.pack_end(self.back_button, expand=False, fill=False)
+
+    def search_entry_changed(self, entry):
+        text = entry.get_text()
+        if self.ins.search_focus:
+            self.ins.search_focus = False
+        elif self.ins.page_changed:
+            self.ins.page_change = False
+            self.filter_search(entry)
+        elif text not in self.ins.search_names:
+            self.filter_search(entry)
+
+    def filter_search(self, entry):
+        text = entry.get_text()
+        current_tab = self.ins.get_current_page()
+        filter = self.pages[current_tab]['filter']
+        filter[RecipeListModel.COL_NAME] = text
+        self.tables[current_tab].set_model(self.recipe_model.tree_model(filter, search_data=text))
+        if self.recipe_model.filtered_nb == 0:
+            if not self.ins.get_nth_page(current_tab).top_bar:
+                self.ins.get_nth_page(current_tab).add_no_result_bar(entry)
+                self.ins.get_nth_page(current_tab).top_bar.set_no_show_all(True)
+            self.ins.get_nth_page(current_tab).top_bar.show()
+            self.ins.get_nth_page(current_tab).scroll.hide()
+        else:
+            if self.ins.get_nth_page(current_tab).top_bar:
+                self.ins.get_nth_page(current_tab).top_bar.hide()
+            self.ins.get_nth_page(current_tab).scroll.show()
+        if entry.get_text() == '':
+            entry.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, False)
+        else:
+            entry.set_icon_sensitive(gtk.ENTRY_ICON_SECONDARY, True)
+
+    def button_click_cb(self, widget, event):
+        path, col = widget.table_tree.get_cursor()
+        tree_model = widget.table_tree.get_model()
+        if path and col.get_title() != 'Included': # else activation is likely a removal
+            properties = {'summary': '', 'name': '', 'version': '', 'revision': '', 'binb': '', 'group': '', 'license': '', 'homepage': '', 'bugtracker': '', 'description': ''}
+            properties['summary'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_SUMMARY)
+            properties['name'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_NAME)
+            properties['version'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_VERSION)
+            properties['revision'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_REVISION)
+            properties['binb'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_BINB)
+            properties['group'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_GROUP)
+            properties['license'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_LIC)
+            properties['homepage'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_HOMEPAGE)
+            properties['bugtracker'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_BUGTRACKER)
+            properties['description'] = tree_model.get_value(tree_model.get_iter(path), RecipeListModel.COL_DESC)
+            self.builder.show_recipe_property_dialog(properties)
+
+    def build_packages_clicked_cb(self, button):
+        self.refresh_tables()
+        self.builder.build_packages()
+
+    def refresh_tables(self):
+        self.ins.reset_entry(self.ins.search, 0)
+        for tab in self.tables:
+            index = self.tables.index(tab)
+            filter = self.pages[index]['filter']
+            tab.set_model(self.recipe_model.tree_model(filter, search_data="", initial=True))
+
+    def back_button_clicked_cb(self, button):
+        self.builder.recipe_model.set_selected_image(self.builder.configuration.initial_selected_image)
+        self.builder.image_configuration_page.update_image_combo(self.builder.recipe_model, self.builder.configuration.initial_selected_image)
+        self.builder.image_configuration_page.update_image_desc()
+        self.builder.show_configuration()
+        self.refresh_tables()
+
+    def refresh_selection(self):
+        self.builder.configuration.selected_image = self.recipe_model.get_selected_image()
+        _, self.builder.configuration.selected_recipes = self.recipe_model.get_selected_recipes()
+        self.ins.show_indicator_icon("Included recipes", len(self.builder.configuration.selected_recipes))
+
+    def toggle_item_idle_cb(self, path, view_tree, cell, pagename):
+        if not self.recipe_model.path_included(path):
+            self.recipe_model.include_item(item_path=path, binb="User Selected", image_contents=False)
+        else:
+            self.pre_fadeout_checkout_include(view_tree, pagename)
+            self.recipe_model.exclude_item(item_path=path)
+            self.render_fadeout(view_tree, cell)
+
+        self.refresh_selection()
+        if not self.builder.customized:
+            self.builder.customized = True
+            self.builder.configuration.selected_image = self.recipe_model.__custom_image__
+            self.builder.rcppkglist_populated()
+
+        self.builder.window_sensitive(True)
+
+        view_model = view_tree.get_model()
+        vpath = self.recipe_model.convert_path_to_vpath(view_model, path)
+        view_tree.set_cursor(vpath)
+
+    def table_toggled_cb(self, table, cell, view_path, toggled_columnid, view_tree, pagename):
+        # Click to include a recipe
+        self.builder.window_sensitive(False)
+        view_model = view_tree.get_model()
+        path = self.recipe_model.convert_vpath_to_path(view_model, view_path)
+        glib.idle_add(self.toggle_item_idle_cb, path, view_tree, cell, pagename)
+
+    def pre_fadeout_checkout_include(self, tree, pagename):
+        #after the fadeout the table will be sorted as before
+        self.sort_column_id = self.recipe_model.sort_column_id
+        self.sort_order = self.recipe_model.sort_order
+
+        #resync the included items to a backup fade include column
+        it = self.recipe_model.get_iter_first()
+        while it:
+            active = self.recipe_model.get_value(it, self.recipe_model.COL_INC)
+            self.recipe_model.set(it, self.recipe_model.COL_FADE_INC, active)
+            it = self.recipe_model.iter_next(it)
+        # Check out a model which base on the column COL_FADE_INC,
+        # it's save the prev state of column COL_INC before do exclude_item
+        filter = { RecipeListModel.COL_FADE_INC:[True] }
+        if pagename == "Included recipes":
+            filter[RecipeListModel.COL_TYPE] = ['recipe', 'packagegroup']
+        elif pagename == "All recipes":
+            filter[RecipeListModel.COL_TYPE] = ['recipe']
+        else:
+            filter[RecipeListModel.COL_TYPE] = ['packagegroup']
+
+        new_model = self.recipe_model.tree_model(filter, excluded_items_ahead=True)
+        tree.set_model(new_model)
+
+    def render_fadeout(self, tree, cell):
+        if (not cell) or (not tree):
+            return
+        to_render_cells = []
+        model = tree.get_model()
+        it = model.get_iter_first()
+        while it:
+            path = model.get_path(it)
+            prev_cell_is_active = model.get_value(it, RecipeListModel.COL_FADE_INC)
+            curr_cell_is_active = model.get_value(it, RecipeListModel.COL_INC)
+            if (prev_cell_is_active == True) and (curr_cell_is_active == False):
+                to_render_cells.append(path)
+            it = model.iter_next(it)
+
+        cell.fadeout(tree, 1000, to_render_cells)
+
+    def after_fadeout_checkin_include(self, table, ctrl, cell, tree, filter):
+        self.recipe_model.sort_column_id = self.sort_column_id
+        self.recipe_model.sort_order = self.sort_order
+        tree.set_model(self.recipe_model.tree_model(filter))
+
+    def set_recipe_curr_tab(self, curr_page):
+        self.ins.set_current_page(curr_page)
diff --git a/bitbake/lib/bb/ui/crumbs/runningbuild.py b/bitbake/lib/bb/ui/crumbs/runningbuild.py
new file mode 100644
index 0000000..16a955d
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/runningbuild.py
@@ -0,0 +1,551 @@
+
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2008        Intel Corporation
+#
+# Authored by Rob Bradford <rob@linux.intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import gobject
+import logging
+import time
+import urllib
+import urllib2
+import pango
+from bb.ui.crumbs.hobcolor import HobColors
+from bb.ui.crumbs.hobwidget import HobWarpCellRendererText, HobCellRendererPixbuf
+
+class RunningBuildModel (gtk.TreeStore):
+    (COL_LOG, COL_PACKAGE, COL_TASK, COL_MESSAGE, COL_ICON, COL_COLOR, COL_NUM_ACTIVE) = range(7)
+
+    def __init__ (self):
+        gtk.TreeStore.__init__ (self,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_STRING,
+                                gobject.TYPE_INT)
+
+    def failure_model_filter(self, model, it):
+        color = model.get(it, self.COL_COLOR)[0]
+        if not color:
+            return False
+        if color == HobColors.ERROR or color == HobColors.WARNING:
+            return True
+        return False
+
+    def failure_model(self):
+        model = self.filter_new()
+        model.set_visible_func(self.failure_model_filter)
+        return model
+
+    def foreach_cell_func(self, model, path, iter, usr_data=None):
+        if model.get_value(iter, self.COL_ICON) == "gtk-execute":
+            model.set(iter, self.COL_ICON, "")
+
+    def close_task_refresh(self):
+        self.foreach(self.foreach_cell_func, None)
+
+class RunningBuild (gobject.GObject):
+    __gsignals__ = {
+          'build-started'   :  (gobject.SIGNAL_RUN_LAST,
+                                gobject.TYPE_NONE,
+                               ()),
+          'build-succeeded' :  (gobject.SIGNAL_RUN_LAST,
+                                gobject.TYPE_NONE,
+                               ()),
+          'build-failed'    :  (gobject.SIGNAL_RUN_LAST,
+                                gobject.TYPE_NONE,
+                               ()),
+          'build-complete'  :  (gobject.SIGNAL_RUN_LAST,
+                                gobject.TYPE_NONE,
+                               ()),
+          'build-aborted'   :  (gobject.SIGNAL_RUN_LAST,
+                                gobject.TYPE_NONE,
+                               ()),
+          'task-started'    :  (gobject.SIGNAL_RUN_LAST,
+                                gobject.TYPE_NONE,
+                               (gobject.TYPE_PYOBJECT,)),
+          'log-error'       :  (gobject.SIGNAL_RUN_LAST,
+                                gobject.TYPE_NONE,
+                               ()),
+          'log-warning'     :  (gobject.SIGNAL_RUN_LAST,
+                                gobject.TYPE_NONE,
+                               ()),
+          'disk-full'       :  (gobject.SIGNAL_RUN_LAST,
+                                gobject.TYPE_NONE,
+                               ()),
+          'no-provider'     :  (gobject.SIGNAL_RUN_LAST,
+                                gobject.TYPE_NONE,
+                               (gobject.TYPE_PYOBJECT,)),
+          'log'             :  (gobject.SIGNAL_RUN_LAST,
+                                gobject.TYPE_NONE,
+                               (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT,)),
+          }
+    pids_to_task = {}
+    tasks_to_iter = {}
+
+    def __init__ (self, sequential=False):
+        gobject.GObject.__init__ (self)
+        self.model = RunningBuildModel()
+        self.sequential = sequential
+        self.buildaborted = False
+
+    def reset (self):
+        self.pids_to_task.clear()
+        self.tasks_to_iter.clear()
+        self.model.clear()
+
+    def handle_event (self, event, pbar=None):
+        # Handle an event from the event queue, this may result in updating
+        # the model and thus the UI. Or it may be to tell us that the build
+        # has finished successfully (or not, as the case may be.)
+
+        parent = None
+        pid = 0
+        package = None
+        task = None
+
+        # If we have a pid attached to this message/event try and get the
+        # (package, task) pair for it. If we get that then get the parent iter
+        # for the message.
+        if hasattr(event, 'pid'):
+            pid = event.pid
+        if hasattr(event, 'process'):
+            pid = event.process
+
+        if pid and pid in self.pids_to_task:
+            (package, task) = self.pids_to_task[pid]
+            parent = self.tasks_to_iter[(package, task)]
+
+        if(isinstance(event, logging.LogRecord)):
+            if event.taskpid == 0 or event.levelno > logging.INFO:
+                self.emit("log", "handle", event)
+            # FIXME: this is a hack! More info in Yocto #1433
+            # http://bugzilla.pokylinux.org/show_bug.cgi?id=1433, temporarily
+            # mask the error message as it's not informative for the user.
+            if event.msg.startswith("Execution of event handler 'run_buildstats' failed"):
+                return
+
+            if (event.levelno < logging.INFO or
+                event.msg.startswith("Running task")):
+                return # don't add these to the list
+
+            if event.levelno >= logging.ERROR:
+                icon = "dialog-error"
+                color = HobColors.ERROR
+                self.emit("log-error")
+            elif event.levelno >= logging.WARNING:
+                icon = "dialog-warning"
+                color = HobColors.WARNING
+                self.emit("log-warning")
+            else:
+                icon = None
+                color = HobColors.OK
+
+            # if we know which package we belong to, we'll append onto its list.
+            # otherwise, we'll jump to the top of the master list
+            if self.sequential or not parent:
+                tree_add = self.model.append
+            else:
+                tree_add = self.model.prepend
+            tree_add(parent,
+                     (None,
+                      package,
+                      task,
+                      event.getMessage(),
+                      icon,
+                      color,
+                      0))
+
+            # if there are warnings while processing a package
+            #  (parent), mark the task with warning color;
+            # in case there are errors, the updates will be
+            #  handled on TaskFailed.
+            if color == HobColors.WARNING and parent:
+                self.model.set(parent, self.model.COL_COLOR, color)
+                if task: #then we have a parent (package), and update it's color
+                    self.model.set(self.tasks_to_iter[(package, None)], self.model.COL_COLOR, color)
+
+        elif isinstance(event, bb.build.TaskStarted):
+            (package, task) = (event._package, event._task)
+
+            # Save out this PID.
+            self.pids_to_task[pid] = (package, task)
+
+            # Check if we already have this package in our model. If so then
+            # that can be the parent for the task. Otherwise we create a new
+            # top level for the package.
+            if ((package, None) in self.tasks_to_iter):
+                parent = self.tasks_to_iter[(package, None)]
+            else:
+                if self.sequential:
+                    add = self.model.append
+                else:
+                    add = self.model.prepend
+                parent = add(None, (None,
+                                    package,
+                                    None,
+                                    "Package: %s" % (package),
+                                    None,
+                                    HobColors.OK,
+                                    0))
+                self.tasks_to_iter[(package, None)] = parent
+
+            # Because this parent package now has an active child mark it as
+            # such.
+            self.model.set(parent, self.model.COL_ICON, "gtk-execute")
+            parent_color = self.model.get(parent, self.model.COL_COLOR)[0]
+            if parent_color != HobColors.ERROR and parent_color != HobColors.WARNING:
+                self.model.set(parent, self.model.COL_COLOR, HobColors.RUNNING)
+
+            # Add an entry in the model for this task
+            i = self.model.append (parent, (None,
+                                            package,
+                                            task,
+                                            "Task: %s" % (task),
+                                            "gtk-execute",
+                                            HobColors.RUNNING,
+                                            0))
+
+            # update the parent's active task count
+            num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] + 1
+            self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active)
+
+            # Save out the iter so that we can find it when we have a message
+            # that we need to attach to a task.
+            self.tasks_to_iter[(package, task)] = i
+
+        elif isinstance(event, bb.build.TaskBase):
+            self.emit("log", "info", event._message)
+            current = self.tasks_to_iter[(package, task)]
+            parent = self.tasks_to_iter[(package, None)]
+
+            # remove this task from the parent's active count
+            num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] - 1
+            self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active)
+
+            if isinstance(event, bb.build.TaskFailed):
+                # Mark the task and parent as failed
+                icon = "dialog-error"
+                color = HobColors.ERROR
+
+                logfile = event.logfile
+                if logfile and os.path.exists(logfile):
+                    with open(logfile) as f:
+                        logdata = f.read()
+                        self.model.append(current, ('pastebin', None, None, logdata, 'gtk-error', HobColors.OK, 0))
+
+                for i in (current, parent):
+                    self.model.set(i, self.model.COL_ICON, icon,
+                                   self.model.COL_COLOR, color)
+            else:
+                # Mark the parent package and the task as inactive,
+                # but make sure to preserve error, warnings and active
+                # states
+                parent_color = self.model.get(parent, self.model.COL_COLOR)[0]
+                task_color = self.model.get(current, self.model.COL_COLOR)[0]
+
+                # Mark the task as inactive
+                self.model.set(current, self.model.COL_ICON, None)
+                if task_color != HobColors.ERROR:
+                    if task_color == HobColors.WARNING:
+                        self.model.set(current, self.model.COL_ICON, 'dialog-warning')
+                    else:
+                        self.model.set(current, self.model.COL_COLOR, HobColors.OK)
+
+                # Mark the parent as inactive
+                if parent_color != HobColors.ERROR:
+                    if parent_color == HobColors.WARNING:
+                        self.model.set(parent, self.model.COL_ICON, "dialog-warning")
+                    else:
+                        self.model.set(parent, self.model.COL_ICON, None)
+                        if num_active == 0:
+                            self.model.set(parent, self.model.COL_COLOR, HobColors.OK)
+
+            # Clear the iters and the pids since when the task goes away the
+            # pid will no longer be used for messages
+            del self.tasks_to_iter[(package, task)]
+            del self.pids_to_task[pid]
+
+        elif isinstance(event, bb.event.BuildStarted):
+
+            self.emit("build-started")
+            self.model.prepend(None, (None,
+                                      None,
+                                      None,
+                                      "Build Started (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
+                                      None,
+                                      HobColors.OK,
+                                      0))
+            if pbar:
+                pbar.update(0, self.progress_total)
+                pbar.set_title(bb.event.getName(event))
+
+        elif isinstance(event, bb.event.BuildCompleted):
+            failures = int (event._failures)
+            self.model.prepend(None, (None,
+                                      None,
+                                      None,
+                                      "Build Completed (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
+                                      None,
+                                      HobColors.OK,
+                                      0))
+
+            # Emit the appropriate signal depending on the number of failures
+            if self.buildaborted:
+                self.emit ("build-aborted")
+                self.buildaborted = False
+            elif (failures >= 1):
+                self.emit ("build-failed")
+            else:
+                self.emit ("build-succeeded")
+            # Emit a generic "build-complete" signal for things wishing to
+            # handle when the build is finished
+            self.emit("build-complete")
+            # reset the all cell's icon indicator
+            self.model.close_task_refresh()
+            if pbar:
+                pbar.set_text(event.msg)
+
+        elif isinstance(event, bb.event.DiskFull):
+            self.buildaborted = True
+            self.emit("disk-full")
+
+        elif isinstance(event, bb.command.CommandFailed):
+            self.emit("log", "error", "Command execution failed: %s" % (event.error))
+            if event.error.startswith("Exited with"):
+                # If the command fails with an exit code we're done, emit the
+                # generic signal for the UI to notify the user
+                self.emit("build-complete")
+                # reset the all cell's icon indicator
+                self.model.close_task_refresh()
+
+        elif isinstance(event, bb.event.CacheLoadStarted) and pbar:
+            pbar.set_title("Loading cache")
+            self.progress_total = event.total
+            pbar.update(0, self.progress_total)
+        elif isinstance(event, bb.event.CacheLoadProgress) and pbar:
+            pbar.update(event.current, self.progress_total)
+        elif isinstance(event, bb.event.CacheLoadCompleted) and pbar:
+            pbar.update(self.progress_total, self.progress_total)
+            pbar.hide()
+        elif isinstance(event, bb.event.ParseStarted) and pbar:
+            if event.total == 0:
+                return
+            pbar.set_title("Processing recipes")
+            self.progress_total = event.total
+            pbar.update(0, self.progress_total)
+        elif isinstance(event, bb.event.ParseProgress) and pbar:
+            pbar.update(event.current, self.progress_total)
+        elif isinstance(event, bb.event.ParseCompleted) and pbar:
+            pbar.hide()
+        #using runqueue events as many as possible to update the progress bar
+        elif isinstance(event, bb.runqueue.runQueueTaskFailed):
+            self.emit("log", "error", "Task %s (%s) failed with exit code '%s'" % (event.taskid, event.taskstring, event.exitcode))
+        elif isinstance(event, bb.runqueue.sceneQueueTaskFailed):
+            self.emit("log", "warn", "Setscene task %s (%s) failed with exit code '%s' - real task will be run instead" \
+                                     % (event.taskid, event.taskstring, event.exitcode))
+        elif isinstance(event, (bb.runqueue.runQueueTaskStarted, bb.runqueue.sceneQueueTaskStarted)):
+            if isinstance(event, bb.runqueue.sceneQueueTaskStarted):
+                self.emit("log", "info", "Running setscene task %d of %d (%s)" % \
+                                         (event.stats.completed + event.stats.active + event.stats.failed + 1,
+                                          event.stats.total, event.taskstring))
+            else:
+                if event.noexec:
+                    tasktype = 'noexec task'
+                else:
+                    tasktype = 'task'
+                self.emit("log", "info", "Running %s %s of %s (ID: %s, %s)" % \
+                                         (tasktype, event.stats.completed + event.stats.active + event.stats.failed + 1,
+                                          event.stats.total, event.taskid, event.taskstring))
+            message = {}
+            message["eventname"] = bb.event.getName(event)
+            num_of_completed = event.stats.completed + event.stats.failed
+            message["current"] = num_of_completed
+            message["total"] = event.stats.total
+            message["title"] = ""
+            message["task"] = event.taskstring
+            self.emit("task-started", message)
+        elif isinstance(event, bb.event.MultipleProviders):
+            self.emit("log", "info", "multiple providers are available for %s%s (%s)" \
+                                     % (event._is_runtime and "runtime " or "", event._item, ", ".join(event._candidates)))
+            self.emit("log", "info", "consider defining a PREFERRED_PROVIDER entry to match %s" % (event._item))
+        elif isinstance(event, bb.event.NoProvider):
+            msg = ""
+            if event._runtime:
+                r = "R"
+            else:
+                r = ""
+
+            extra = ''
+            if not event._reasons:
+                if event._close_matches:
+                    extra = ". Close matches:\n  %s" % '\n  '.join(event._close_matches)
+
+            if event._dependees:
+                msg = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s\n" % (r, event._item, ", ".join(event._dependees), r, extra)
+            else:
+                msg = "Nothing %sPROVIDES '%s'%s\n" % (r, event._item, extra)
+            if event._reasons:
+                for reason in event._reasons:
+                    msg += ("%s\n" % reason)
+            self.emit("no-provider", msg)
+            self.emit("log", "error", msg)
+        elif isinstance(event, bb.event.LogExecTTY):
+            icon = "dialog-warning"
+            color = HobColors.WARNING
+            if self.sequential or not parent:
+                tree_add = self.model.append
+            else:
+                tree_add = self.model.prepend
+            tree_add(parent,
+                     (None,
+                      package,
+                      task,
+                      event.msg,
+                      icon,
+                      color,
+                      0))
+        else:
+            if not isinstance(event, (bb.event.BuildBase,
+                                      bb.event.StampUpdate,
+                                      bb.event.ConfigParsed,
+                                      bb.event.RecipeParsed,
+                                      bb.event.RecipePreFinalise,
+                                      bb.runqueue.runQueueEvent,
+                                      bb.runqueue.runQueueExitWait,
+                                      bb.event.OperationStarted,
+                                      bb.event.OperationCompleted,
+                                      bb.event.OperationProgress)):
+                self.emit("log", "error", "Unknown event: %s" % (event.error if hasattr(event, 'error') else 'error'))
+
+        return
+
+
+def do_pastebin(text):
+    url = 'http://pastebin.com/api_public.php'
+    params = {'paste_code': text, 'paste_format': 'text'}
+
+    req = urllib2.Request(url, urllib.urlencode(params))
+    response = urllib2.urlopen(req)
+    paste_url = response.read()
+
+    return paste_url
+
+
+class RunningBuildTreeView (gtk.TreeView):
+    __gsignals__ = {
+        "button_press_event" : "override"
+        }
+    def __init__ (self, readonly=False, hob=False):
+        gtk.TreeView.__init__ (self)
+        self.readonly = readonly
+
+        # The icon that indicates whether we're building or failed.
+        # add 'hob' flag because there has not only hob to share this code
+        if hob:
+            renderer = HobCellRendererPixbuf ()
+        else:
+            renderer = gtk.CellRendererPixbuf()
+        col = gtk.TreeViewColumn ("Status", renderer)
+        col.add_attribute (renderer, "icon-name", 4)
+        self.append_column (col)
+
+        # The message of the build.
+        # add 'hob' flag because there has not only hob to share this code
+        if hob:
+            self.message_renderer = HobWarpCellRendererText (col_number=1)
+        else:
+            self.message_renderer = gtk.CellRendererText ()
+        self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=3)
+        self.message_column.add_attribute(self.message_renderer, 'background', 5)
+        self.message_renderer.set_property('editable', (not self.readonly))
+        self.append_column (self.message_column)
+
+    def do_button_press_event(self, event):
+        gtk.TreeView.do_button_press_event(self, event)
+
+        if event.button == 3:
+            selection = super(RunningBuildTreeView, self).get_selection()
+            (model, it) = selection.get_selected()
+            if it is not None:
+                can_paste = model.get(it, model.COL_LOG)[0]
+                if can_paste == 'pastebin':
+                    # build a simple menu with a pastebin option
+                    menu = gtk.Menu()
+                    menuitem = gtk.MenuItem("Copy")
+                    menu.append(menuitem)
+                    menuitem.connect("activate", self.clipboard_handler, (model, it))
+                    menuitem.show()
+                    menuitem = gtk.MenuItem("Send log to pastebin")
+                    menu.append(menuitem)
+                    menuitem.connect("activate", self.pastebin_handler, (model, it))
+                    menuitem.show()
+                    menu.show()
+                    menu.popup(None, None, None, event.button, event.time)
+
+    def _add_to_clipboard(self, clipping):
+        """
+        Add the contents of clipping to the system clipboard.
+        """
+        clipboard = gtk.clipboard_get()
+        clipboard.set_text(clipping)
+        clipboard.store()
+
+    def pastebin_handler(self, widget, data):
+        """
+        Send the log data to pastebin, then add the new paste url to the
+        clipboard.
+        """
+        (model, it) = data
+        paste_url = do_pastebin(model.get(it, model.COL_MESSAGE)[0])
+
+        # @todo Provide visual feedback to the user that it is done and that
+        # it worked.
+        print paste_url
+
+        self._add_to_clipboard(paste_url)
+
+    def clipboard_handler(self, widget, data):
+        """
+        """
+        (model, it) = data
+        message = model.get(it, model.COL_MESSAGE)[0]
+
+        self._add_to_clipboard(message)
+
+class BuildFailureTreeView(gtk.TreeView):
+
+    def __init__ (self):
+        gtk.TreeView.__init__(self)
+        self.set_rules_hint(False)
+        self.set_headers_visible(False)
+        self.get_selection().set_mode(gtk.SELECTION_SINGLE)
+
+        # The icon that indicates whether we're building or failed.
+        renderer = HobCellRendererPixbuf ()
+        col = gtk.TreeViewColumn ("Status", renderer)
+        col.add_attribute (renderer, "icon-name", RunningBuildModel.COL_ICON)
+        self.append_column (col)
+
+        # The message of the build.
+        self.message_renderer = HobWarpCellRendererText (col_number=1)
+        self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=RunningBuildModel.COL_MESSAGE, background=RunningBuildModel.COL_COLOR)
+        self.append_column (self.message_column)
diff --git a/bitbake/lib/bb/ui/crumbs/sanitycheckpage.py b/bitbake/lib/bb/ui/crumbs/sanitycheckpage.py
new file mode 100644
index 0000000..76ce2ec
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/sanitycheckpage.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2012        Intel Corporation
+#
+# Authored by Bogdan Marinescu <bogdan.a.marinescu@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk, gobject
+from bb.ui.crumbs.progressbar import HobProgressBar
+from bb.ui.crumbs.hobwidget import hic
+from bb.ui.crumbs.hobpages import HobPage
+
+#
+# SanityCheckPage
+#
+class SanityCheckPage (HobPage):
+
+    def __init__(self, builder):
+        super(SanityCheckPage, self).__init__(builder)
+        self.running = False
+        self.create_visual_elements()
+        self.show_all()
+
+    def make_label(self, text, bold=True):
+        label = gtk.Label()
+        label.set_alignment(0.0, 0.5)
+        mark = "<span %s>%s</span>" % (self.span_tag('x-large', 'bold') if bold else self.span_tag('medium'), text)
+        label.set_markup(mark)
+        return label
+
+    def start(self):
+        if not self.running:
+          self.running = True
+          gobject.timeout_add(100, self.timer_func)
+
+    def stop(self):
+        self.running = False
+
+    def is_running(self):
+        return self.running
+
+    def timer_func(self):
+        self.progress_bar.pulse()
+        return self.running
+
+    def create_visual_elements(self):
+        # Table'd layout. 'rows' and 'cols' give the table size
+        rows, cols = 30, 50
+        self.table = gtk.Table(rows, cols, True)
+        self.pack_start(self.table, expand=False, fill=False)
+        sx, sy = 2, 2
+        # 'info' icon
+        image = gtk.Image()
+        image.set_from_file(hic.ICON_INFO_DISPLAY_FILE)
+        self.table.attach(image, sx, sx + 2, sy, sy + 3 )
+        image.show()
+        # 'Checking' message
+        label = self.make_label('Hob is checking for correct build system setup')
+        self.table.attach(label, sx + 2, cols, sy, sy + 3, xpadding=5 )
+        label.show()
+        # 'Shouldn't take long' message.
+        label = self.make_label("The check shouldn't take long.", False)
+        self.table.attach(label, sx + 2, cols, sy + 3, sy + 4, xpadding=5)
+        label.show()
+        # Progress bar
+        self.progress_bar = HobProgressBar()
+        self.table.attach(self.progress_bar, sx + 2, cols - 3, sy + 5, sy + 7, xpadding=5)
+        self.progress_bar.show()
+        # All done
+        self.table.show()
+
diff --git a/bitbake/lib/bb/ui/crumbs/utils.py b/bitbake/lib/bb/ui/crumbs/utils.py
new file mode 100644
index 0000000..939864f
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/utils.py
@@ -0,0 +1,34 @@
+#
+# BitBake UI Utils 
+#
+# Copyright (C) 2012 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# This utility method looks for xterm or vte and return the 
+# frist to exist, currently we are keeping this simple, but 
+# we will likely move the oe.terminal implementation into 
+# bitbake which will allow more flexibility.
+
+import os
+import bb
+
+def which_terminal():
+    term = bb.utils.which(os.environ["PATH"], "xterm")
+    if term:
+        return term + " -e "
+    term = bb.utils.which(os.environ["PATH"], "vte")
+    if term:
+        return term + " -c "
+    return None
diff --git a/bitbake/lib/bb/ui/depexp.py b/bitbake/lib/bb/ui/depexp.py
new file mode 100644
index 0000000..240aafc
--- /dev/null
+++ b/bitbake/lib/bb/ui/depexp.py
@@ -0,0 +1,333 @@
+#
+# BitBake Graphical GTK based Dependency Explorer
+#
+# Copyright (C) 2007        Ross Burton
+# Copyright (C) 2007 - 2008 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import sys
+import gobject
+import gtk
+import Queue
+import threading
+import xmlrpclib
+import bb
+import bb.event
+from bb.ui.crumbs.progressbar import HobProgressBar
+
+# Package Model
+(COL_PKG_NAME) = (0)
+
+# Dependency Model
+(TYPE_DEP, TYPE_RDEP) = (0, 1)
+(COL_DEP_TYPE, COL_DEP_PARENT, COL_DEP_PACKAGE) = (0, 1, 2)
+
+
+class PackageDepView(gtk.TreeView):
+    def __init__(self, model, dep_type, label):
+        gtk.TreeView.__init__(self)
+        self.current = None
+        self.dep_type = dep_type
+        self.filter_model = model.filter_new()
+        self.filter_model.set_visible_func(self._filter)
+        self.set_model(self.filter_model)
+        #self.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
+        self.append_column(gtk.TreeViewColumn(label, gtk.CellRendererText(), text=COL_DEP_PACKAGE))
+
+    def _filter(self, model, iter):
+        (this_type, package) = model.get(iter, COL_DEP_TYPE, COL_DEP_PARENT)
+        if this_type != self.dep_type: return False
+        return package == self.current
+
+    def set_current_package(self, package):
+        self.current = package
+        self.filter_model.refilter()
+
+
+class PackageReverseDepView(gtk.TreeView):
+    def __init__(self, model, label):
+        gtk.TreeView.__init__(self)
+        self.current = None
+        self.filter_model = model.filter_new()
+        self.filter_model.set_visible_func(self._filter)
+        self.set_model(self.filter_model)
+        self.append_column(gtk.TreeViewColumn(label, gtk.CellRendererText(), text=COL_DEP_PARENT))
+
+    def _filter(self, model, iter):
+        package = model.get_value(iter, COL_DEP_PACKAGE)
+        return package == self.current
+
+    def set_current_package(self, package):
+        self.current = package
+        self.filter_model.refilter()
+
+
+class DepExplorer(gtk.Window):
+    def __init__(self):
+        gtk.Window.__init__(self)
+        self.set_title("Dependency Explorer")
+        self.set_default_size(500, 500)
+        self.connect("delete-event", gtk.main_quit)
+
+        # Create the data models
+        self.pkg_model = gtk.ListStore(gobject.TYPE_STRING)
+        self.pkg_model.set_sort_column_id(COL_PKG_NAME, gtk.SORT_ASCENDING)
+        self.depends_model = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING, gobject.TYPE_STRING)
+        self.depends_model.set_sort_column_id(COL_DEP_PACKAGE, gtk.SORT_ASCENDING)
+
+        pane = gtk.HPaned()
+        pane.set_position(250)
+        self.add(pane)
+
+        # The master list of packages
+        scrolled = gtk.ScrolledWindow()
+        scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+        scrolled.set_shadow_type(gtk.SHADOW_IN)
+
+        self.pkg_treeview = gtk.TreeView(self.pkg_model)
+        self.pkg_treeview.get_selection().connect("changed", self.on_cursor_changed)
+        column = gtk.TreeViewColumn("Package", gtk.CellRendererText(), text=COL_PKG_NAME)
+        self.pkg_treeview.append_column(column)
+        pane.add1(scrolled)
+        scrolled.add(self.pkg_treeview)
+
+        box = gtk.VBox(homogeneous=True, spacing=4)
+
+        # Runtime Depends
+        scrolled = gtk.ScrolledWindow()
+        scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+        scrolled.set_shadow_type(gtk.SHADOW_IN)
+        self.rdep_treeview = PackageDepView(self.depends_model, TYPE_RDEP, "Runtime Depends")
+        self.rdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
+        scrolled.add(self.rdep_treeview)
+        box.add(scrolled)
+
+        # Build Depends
+        scrolled = gtk.ScrolledWindow()
+        scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+        scrolled.set_shadow_type(gtk.SHADOW_IN)
+        self.dep_treeview = PackageDepView(self.depends_model, TYPE_DEP, "Build Depends")
+        self.dep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
+        scrolled.add(self.dep_treeview)
+        box.add(scrolled)
+        pane.add2(box)
+
+        # Reverse Depends
+        scrolled = gtk.ScrolledWindow()
+        scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+        scrolled.set_shadow_type(gtk.SHADOW_IN)
+        self.revdep_treeview = PackageReverseDepView(self.depends_model, "Reverse Depends")
+        self.revdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PARENT)
+        scrolled.add(self.revdep_treeview)
+        box.add(scrolled)
+        pane.add2(box)
+
+        self.show_all()
+
+    def on_package_activated(self, treeview, path, column, data_col):
+        model = treeview.get_model()
+        package = model.get_value(model.get_iter(path), data_col)
+
+        pkg_path = []
+        def finder(model, path, iter, needle):
+            package = model.get_value(iter, COL_PKG_NAME)
+            if package == needle:
+                pkg_path.append(path)
+                return True
+            else:
+                return False
+        self.pkg_model.foreach(finder, package)
+        if pkg_path:
+            self.pkg_treeview.get_selection().select_path(pkg_path[0])
+            self.pkg_treeview.scroll_to_cell(pkg_path[0])
+
+    def on_cursor_changed(self, selection):
+        (model, it) = selection.get_selected()
+        if it is None:
+            current_package = None
+        else:
+            current_package = model.get_value(it, COL_PKG_NAME)
+        self.rdep_treeview.set_current_package(current_package)
+        self.dep_treeview.set_current_package(current_package)
+        self.revdep_treeview.set_current_package(current_package)
+
+
+    def parse(self, depgraph):
+        for package in depgraph["pn"]:
+            self.pkg_model.insert(0, (package,))
+
+        for package in depgraph["depends"]:
+            for depend in depgraph["depends"][package]:
+                self.depends_model.insert (0, (TYPE_DEP, package, depend))
+
+        for package in depgraph["rdepends-pn"]:
+            for rdepend in depgraph["rdepends-pn"][package]:
+                self.depends_model.insert (0, (TYPE_RDEP, package, rdepend))
+
+
+class gtkthread(threading.Thread):
+    quit = threading.Event()
+    def __init__(self, shutdown):
+        threading.Thread.__init__(self)
+        self.setDaemon(True)
+        self.shutdown = shutdown
+
+    def run(self):
+        gobject.threads_init()
+        gtk.gdk.threads_init()
+        gtk.main()
+        gtkthread.quit.set()
+
+
+def main(server, eventHandler, params):
+    try:
+        params.updateFromServer(server)
+        cmdline = params.parseActions()
+        if not cmdline:
+            print("Nothing to do.  Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
+            return 1
+        if 'msg' in cmdline and cmdline['msg']:
+            print(cmdline['msg'])
+            return 1
+        cmdline = cmdline['action']
+        if not cmdline or cmdline[0] != "generateDotGraph":
+            print("This UI requires the -g option")
+            return 1
+        ret, error = server.runCommand(["generateDepTreeEvent", cmdline[1], cmdline[2]])
+        if error:
+            print("Error running command '%s': %s" % (cmdline, error))
+            return 1
+        elif ret != True:
+            print("Error running command '%s': returned %s" % (cmdline, ret))
+            return 1
+    except xmlrpclib.Fault as x:
+        print("XMLRPC Fault getting commandline:\n %s" % x)
+        return
+
+    try:
+        gtk.init_check()
+    except RuntimeError:
+        sys.stderr.write("Please set DISPLAY variable before running this command \n")
+        return
+
+    shutdown = 0
+
+    gtkgui = gtkthread(shutdown)
+    gtkgui.start()
+
+    gtk.gdk.threads_enter()
+    dep = DepExplorer()
+    bardialog = gtk.Dialog(parent=dep,
+                           flags=gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT)
+    bardialog.set_default_size(400, 50)
+    pbar = HobProgressBar()
+    bardialog.vbox.pack_start(pbar)
+    bardialog.show_all()
+    bardialog.connect("delete-event", gtk.main_quit)
+    gtk.gdk.threads_leave()
+
+    progress_total = 0
+    while True:
+        try:
+            event = eventHandler.waitEvent(0.25)
+            if gtkthread.quit.isSet():
+                _, error = server.runCommand(["stateForceShutdown"])
+                if error:
+                    print('Unable to cleanly stop: %s' % error)
+                break
+
+            if event is None:
+                continue
+
+            if isinstance(event, bb.event.CacheLoadStarted):
+                progress_total = event.total
+                gtk.gdk.threads_enter()
+                bardialog.set_title("Loading Cache")
+                pbar.update(0)
+                gtk.gdk.threads_leave()
+
+            if isinstance(event, bb.event.CacheLoadProgress):
+                x = event.current
+                gtk.gdk.threads_enter()
+                pbar.update(x * 1.0 / progress_total)
+                pbar.set_title('')
+                gtk.gdk.threads_leave()
+                continue
+
+            if isinstance(event, bb.event.CacheLoadCompleted):
+                bardialog.hide()
+                continue
+
+            if isinstance(event, bb.event.ParseStarted):
+                progress_total = event.total
+                if progress_total == 0:
+                    continue
+                gtk.gdk.threads_enter()
+                pbar.update(0)
+                bardialog.set_title("Processing recipes")
+
+                gtk.gdk.threads_leave()
+
+            if isinstance(event, bb.event.ParseProgress):
+                x = event.current
+                gtk.gdk.threads_enter()
+                pbar.update(x * 1.0 / progress_total)
+                pbar.set_title('')
+                gtk.gdk.threads_leave()
+                continue
+
+            if isinstance(event, bb.event.ParseCompleted):
+                bardialog.hide()
+                continue
+
+            if isinstance(event, bb.event.DepTreeGenerated):
+                gtk.gdk.threads_enter()
+                dep.parse(event._depgraph)
+                gtk.gdk.threads_leave()
+
+            if isinstance(event, bb.command.CommandCompleted):
+                continue
+
+            if isinstance(event, bb.command.CommandFailed):
+                print("Command execution failed: %s" % event.error)
+                return event.exitcode
+
+            if isinstance(event, bb.command.CommandExit):
+                return event.exitcode
+
+            if isinstance(event, bb.cooker.CookerExit):
+                break
+
+            continue
+        except EnvironmentError as ioerror:
+            # ignore interrupted io
+            if ioerror.args[0] == 4:
+                pass
+        except KeyboardInterrupt:
+            if shutdown == 2:
+                print("\nThird Keyboard Interrupt, exit.\n")
+                break
+            if shutdown == 1:
+                print("\nSecond Keyboard Interrupt, stopping...\n")
+                _, error = server.runCommand(["stateForceShutdown"])
+                if error:
+                    print('Unable to cleanly stop: %s' % error)
+            if shutdown == 0:
+                print("\nKeyboard Interrupt, closing down...\n")
+                _, error = server.runCommand(["stateShutdown"])
+                if error:
+                    print('Unable to cleanly shutdown: %s' % error)
+            shutdown = shutdown + 1
+            pass
diff --git a/bitbake/lib/bb/ui/goggle.py b/bitbake/lib/bb/ui/goggle.py
new file mode 100644
index 0000000..f4ee7b4
--- /dev/null
+++ b/bitbake/lib/bb/ui/goggle.py
@@ -0,0 +1,121 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2008        Intel Corporation
+#
+# Authored by Rob Bradford <rob@linux.intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gobject
+import gtk
+import xmlrpclib
+from bb.ui.crumbs.runningbuild import RunningBuildTreeView, RunningBuild
+from bb.ui.crumbs.progress import ProgressBar
+
+import Queue
+
+
+def event_handle_idle_func (eventHandler, build, pbar):
+
+    # Consume as many messages as we can in the time available to us
+    event = eventHandler.getEvent()
+    while event:
+        build.handle_event (event, pbar)
+        event = eventHandler.getEvent()
+
+    return True
+
+def scroll_tv_cb (model, path, iter, view):
+    view.scroll_to_cell (path)
+
+
+# @todo hook these into the GUI so the user has feedback...
+def running_build_failed_cb (running_build):
+    pass
+
+
+def running_build_succeeded_cb (running_build):
+    pass
+
+
+class MainWindow (gtk.Window):
+    def __init__ (self):
+        gtk.Window.__init__ (self, gtk.WINDOW_TOPLEVEL)
+
+        # Setup tree view and the scrolled window
+        scrolled_window = gtk.ScrolledWindow ()
+        self.add (scrolled_window)
+        self.cur_build_tv = RunningBuildTreeView()
+        self.connect("delete-event", gtk.main_quit)
+        self.set_default_size(640, 480)
+        scrolled_window.add (self.cur_build_tv)
+
+
+def main (server, eventHandler, params):
+    gobject.threads_init()
+    gtk.gdk.threads_init()
+
+    window = MainWindow ()
+    window.show_all ()
+    pbar = ProgressBar(window)
+    pbar.connect("delete-event", gtk.main_quit)
+
+    # Create the object for the current build
+    running_build = RunningBuild ()
+    window.cur_build_tv.set_model (running_build.model)
+    running_build.model.connect("row-inserted", scroll_tv_cb, window.cur_build_tv)
+    running_build.connect ("build-succeeded", running_build_succeeded_cb)
+    running_build.connect ("build-failed", running_build_failed_cb)
+
+    try:
+        params.updateFromServer(server)
+        cmdline = params.parseActions()
+        if not cmdline:
+            print("Nothing to do.  Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
+            return 1
+        if 'msg' in cmdline and cmdline['msg']:
+            logger.error(cmdline['msg'])
+            return 1
+        cmdline = cmdline['action']
+        ret, error = server.runCommand(cmdline)
+        if error:
+            print("Error running command '%s': %s" % (cmdline, error))
+            return 1
+        elif ret != True:
+            print("Error running command '%s': returned %s" % (cmdline, ret))
+            return 1
+    except xmlrpclib.Fault as x:
+        print("XMLRPC Fault getting commandline:\n %s" % x)
+        return 1
+
+    # Use a timeout function for probing the event queue to find out if we
+    # have a message waiting for us.
+    gobject.timeout_add (100,
+                         event_handle_idle_func,
+                         eventHandler,
+                         running_build,
+                         pbar)
+
+    try:
+        gtk.main()
+    except EnvironmentError as ioerror:
+        # ignore interrupted io
+        if ioerror.args[0] == 4:
+            pass
+    except KeyboardInterrupt:
+        pass
+    finally:
+        server.runCommand(["stateForceShutdown"])
+
diff --git a/bitbake/lib/bb/ui/hob.py b/bitbake/lib/bb/ui/hob.py
new file mode 100755
index 0000000..da5b411
--- /dev/null
+++ b/bitbake/lib/bb/ui/hob.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2011        Intel Corporation
+#
+# Authored by Joshua Lock <josh@linux.intel.com>
+# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import sys
+import os
+requirements = "FATAL: Hob requires Gtk+ 2.20.0 or higher, PyGtk 2.21.0 or higher"
+try:
+    import gobject
+    import gtk
+    import pygtk
+    pygtk.require('2.0') # to be certain we don't have gtk+ 1.x !?!
+    gtkver = gtk.gtk_version
+    pygtkver = gtk.pygtk_version
+    if gtkver  < (2, 20, 0) or pygtkver < (2, 21, 0):
+        sys.exit("%s,\nYou have Gtk+ %s and PyGtk %s." % (requirements,
+                ".".join(map(str, gtkver)),
+                ".".join(map(str, pygtkver))))
+except ImportError as exc:
+    sys.exit("%s (%s)." % (requirements, str(exc)))
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
+try:
+    import bb
+except RuntimeError as exc:
+    sys.exit(str(exc))
+from bb.ui import uihelper
+from bb.ui.crumbs.hoblistmodel import RecipeListModel, PackageListModel
+from bb.ui.crumbs.hobeventhandler import HobHandler
+from bb.ui.crumbs.builder import Builder
+
+featureSet = [bb.cooker.CookerFeatures.HOB_EXTRA_CACHES, bb.cooker.CookerFeatures.BASEDATASTORE_TRACKING]
+
+def event_handle_idle_func(eventHandler, hobHandler):
+    # Consume as many messages as we can in the time available to us
+    if not eventHandler:
+        return False
+    event = eventHandler.getEvent()
+    while event:
+        hobHandler.handle_event(event)
+        event = eventHandler.getEvent()
+    return True
+
+_evt_list = [ "bb.runqueue.runQueueExitWait", "bb.event.LogExecTTY", "logging.LogRecord",
+              "bb.build.TaskFailed", "bb.build.TaskBase", "bb.event.ParseStarted",
+              "bb.event.ParseProgress", "bb.event.ParseCompleted", "bb.event.CacheLoadStarted",
+              "bb.event.CacheLoadProgress", "bb.event.CacheLoadCompleted", "bb.command.CommandFailed",
+              "bb.command.CommandExit", "bb.command.CommandCompleted",  "bb.cooker.CookerExit",
+              "bb.event.MultipleProviders", "bb.event.NoProvider", "bb.runqueue.sceneQueueTaskStarted",
+              "bb.runqueue.runQueueTaskStarted", "bb.runqueue.runQueueTaskFailed", "bb.runqueue.sceneQueueTaskFailed",
+              "bb.event.BuildBase", "bb.build.TaskStarted", "bb.build.TaskSucceeded", "bb.build.TaskFailedSilent",
+              "bb.event.SanityCheckPassed", "bb.event.SanityCheckFailed", "bb.event.PackageInfo",
+              "bb.event.TargetsTreeGenerated", "bb.event.ConfigFilesFound", "bb.event.ConfigFilePathFound",
+              "bb.event.FilesMatchingFound", "bb.event.NetworkTestFailed", "bb.event.NetworkTestPassed",
+              "bb.event.BuildStarted", "bb.event.BuildCompleted", "bb.event.DiskFull"]
+
+def main (server, eventHandler, params):
+    params.updateFromServer(server)
+    gobject.threads_init()
+
+    # That indicates whether the Hob and the bitbake server are
+    # running on different machines
+    # recipe model and package model
+    recipe_model = RecipeListModel()
+    package_model = PackageListModel()
+
+    llevel, debug_domains = bb.msg.constructLogOptions()
+    server.runCommand(["setEventMask", server.getEventHandle(), llevel, debug_domains, _evt_list])
+    hobHandler = HobHandler(server, recipe_model, package_model)
+    builder = Builder(hobHandler, recipe_model, package_model)
+
+    # This timeout function regularly probes the event queue to find out if we
+    # have any messages waiting for us.
+    gobject.timeout_add(10, event_handle_idle_func, eventHandler, hobHandler)
+
+    try:
+        gtk.main()
+    except EnvironmentError as ioerror:
+        # ignore interrupted io
+        if ioerror.args[0] == 4:
+            pass
+    finally:
+        hobHandler.cancel_build(force = True)
+
+if __name__ == "__main__":
+    try:
+        ret = main()
+    except Exception:
+        ret = 1
+        import traceback
+        traceback.print_exc(15)
+    sys.exit(ret)
diff --git a/bitbake/lib/bb/ui/icons/images/images_display.png b/bitbake/lib/bb/ui/icons/images/images_display.png
new file mode 100644
index 0000000..a7f8710
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/images/images_display.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/images/images_hover.png b/bitbake/lib/bb/ui/icons/images/images_hover.png
new file mode 100644
index 0000000..2d9cd99
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/images/images_hover.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/add-hover.png b/bitbake/lib/bb/ui/icons/indicators/add-hover.png
new file mode 100644
index 0000000..526df77
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/add-hover.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/add.png b/bitbake/lib/bb/ui/icons/indicators/add.png
new file mode 100644
index 0000000..31e7090
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/add.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/alert.png b/bitbake/lib/bb/ui/icons/indicators/alert.png
new file mode 100644
index 0000000..d1c6f55
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/alert.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/confirmation.png b/bitbake/lib/bb/ui/icons/indicators/confirmation.png
new file mode 100644
index 0000000..3a5402d
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/confirmation.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/denied.png b/bitbake/lib/bb/ui/icons/indicators/denied.png
new file mode 100644
index 0000000..ee35c7d
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/denied.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/error.png b/bitbake/lib/bb/ui/icons/indicators/error.png
new file mode 100644
index 0000000..d06a8c1
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/error.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/info.png b/bitbake/lib/bb/ui/icons/indicators/info.png
new file mode 100644
index 0000000..ee8e8d8
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/info.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/issues.png b/bitbake/lib/bb/ui/icons/indicators/issues.png
new file mode 100644
index 0000000..b0c7461
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/issues.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/refresh.png b/bitbake/lib/bb/ui/icons/indicators/refresh.png
new file mode 100644
index 0000000..eb6c419
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/refresh.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/remove-hover.png b/bitbake/lib/bb/ui/icons/indicators/remove-hover.png
new file mode 100644
index 0000000..aa57c69
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/remove-hover.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/remove.png b/bitbake/lib/bb/ui/icons/indicators/remove.png
new file mode 100644
index 0000000..05c3c29
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/remove.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/indicators/tick.png b/bitbake/lib/bb/ui/icons/indicators/tick.png
new file mode 100644
index 0000000..beaad36
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/indicators/tick.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/info/info_display.png b/bitbake/lib/bb/ui/icons/info/info_display.png
new file mode 100644
index 0000000..5afbba2
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/info/info_display.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/info/info_hover.png b/bitbake/lib/bb/ui/icons/info/info_hover.png
new file mode 100644
index 0000000..f9d294d
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/info/info_hover.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/layers/layers_display.png b/bitbake/lib/bb/ui/icons/layers/layers_display.png
new file mode 100644
index 0000000..b7f9053
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/layers/layers_display.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/layers/layers_hover.png b/bitbake/lib/bb/ui/icons/layers/layers_hover.png
new file mode 100644
index 0000000..0bf3ce0
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/layers/layers_hover.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/packages/packages_display.png b/bitbake/lib/bb/ui/icons/packages/packages_display.png
new file mode 100644
index 0000000..f5d0a50
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/packages/packages_display.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/packages/packages_hover.png b/bitbake/lib/bb/ui/icons/packages/packages_hover.png
new file mode 100644
index 0000000..c081165
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/packages/packages_hover.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/recipe/recipe_display.png b/bitbake/lib/bb/ui/icons/recipe/recipe_display.png
new file mode 100644
index 0000000..e9809bc
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/recipe/recipe_display.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/recipe/recipe_hover.png b/bitbake/lib/bb/ui/icons/recipe/recipe_hover.png
new file mode 100644
index 0000000..7e48da9
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/recipe/recipe_hover.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/settings/settings_display.png b/bitbake/lib/bb/ui/icons/settings/settings_display.png
new file mode 100644
index 0000000..88c464d
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/settings/settings_display.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/settings/settings_hover.png b/bitbake/lib/bb/ui/icons/settings/settings_hover.png
new file mode 100644
index 0000000..d92a0bf
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/settings/settings_hover.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/templates/templates_display.png b/bitbake/lib/bb/ui/icons/templates/templates_display.png
new file mode 100644
index 0000000..153c7af
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/templates/templates_display.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/icons/templates/templates_hover.png b/bitbake/lib/bb/ui/icons/templates/templates_hover.png
new file mode 100644
index 0000000..afb7165
--- /dev/null
+++ b/bitbake/lib/bb/ui/icons/templates/templates_hover.png
Binary files differ
diff --git a/bitbake/lib/bb/ui/knotty.py b/bitbake/lib/bb/ui/knotty.py
new file mode 100644
index 0000000..2bee242
--- /dev/null
+++ b/bitbake/lib/bb/ui/knotty.py
@@ -0,0 +1,564 @@
+#
+# BitBake (No)TTY UI Implementation
+#
+# Handling output to TTYs or files (no TTY)
+#
+# Copyright (C) 2006-2012 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from __future__ import division
+
+import os
+import sys
+import xmlrpclib
+import logging
+import progressbar
+import signal
+import bb.msg
+import time
+import fcntl
+import struct
+import copy
+import atexit
+from bb.ui import uihelper
+
+featureSet = [bb.cooker.CookerFeatures.SEND_SANITYEVENTS]
+
+logger = logging.getLogger("BitBake")
+interactive = sys.stdout.isatty()
+
+class BBProgress(progressbar.ProgressBar):
+    def __init__(self, msg, maxval):
+        self.msg = msg
+        widgets = [progressbar.Percentage(), ' ', progressbar.Bar(), ' ',
+           progressbar.ETA()]
+
+        try:
+            self._resize_default = signal.getsignal(signal.SIGWINCH)
+        except:
+            self._resize_default = None
+        progressbar.ProgressBar.__init__(self, maxval, [self.msg + ": "] + widgets, fd=sys.stdout)
+
+    def _handle_resize(self, signum, frame):
+        progressbar.ProgressBar._handle_resize(self, signum, frame)
+        if self._resize_default:
+            self._resize_default(signum, frame)
+    def finish(self):
+        progressbar.ProgressBar.finish(self)
+        if self._resize_default:
+            signal.signal(signal.SIGWINCH, self._resize_default)
+
+class NonInteractiveProgress(object):
+    fobj = sys.stdout
+
+    def __init__(self, msg, maxval):
+        self.msg = msg
+        self.maxval = maxval
+
+    def start(self):
+        self.fobj.write("%s..." % self.msg)
+        self.fobj.flush()
+        return self
+
+    def update(self, value):
+        pass
+
+    def finish(self):
+        self.fobj.write("done.\n")
+        self.fobj.flush()
+
+def new_progress(msg, maxval):
+    if interactive:
+        return BBProgress(msg, maxval)
+    else:
+        return NonInteractiveProgress(msg, maxval)
+
+def pluralise(singular, plural, qty):
+    if(qty == 1):
+        return singular % qty
+    else:
+        return plural % qty
+
+
+class InteractConsoleLogFilter(logging.Filter):
+    def __init__(self, tf, format):
+        self.tf = tf
+        self.format = format
+
+    def filter(self, record):
+        if record.levelno == self.format.NOTE and (record.msg.startswith("Running") or record.msg.startswith("recipe ")):
+            return False
+        self.tf.clearFooter()
+        return True
+
+class TerminalFilter(object):
+    columns = 80
+
+    def sigwinch_handle(self, signum, frame):
+        self.columns = self.getTerminalColumns()
+        if self._sigwinch_default:
+            self._sigwinch_default(signum, frame)
+
+    def getTerminalColumns(self):
+        def ioctl_GWINSZ(fd):
+            try:
+                cr = struct.unpack('hh', fcntl.ioctl(fd, self.termios.TIOCGWINSZ, '1234'))
+            except:
+                return None
+            return cr
+        cr = ioctl_GWINSZ(sys.stdout.fileno())
+        if not cr:
+            try:
+                fd = os.open(os.ctermid(), os.O_RDONLY)
+                cr = ioctl_GWINSZ(fd)
+                os.close(fd)
+            except:
+                pass
+        if not cr:
+            try:
+                cr = (env['LINES'], env['COLUMNS'])
+            except:
+                cr = (25, 80)
+        return cr[1]
+
+    def __init__(self, main, helper, console, errconsole, format):
+        self.main = main
+        self.helper = helper
+        self.cuu = None
+        self.stdinbackup = None
+        self.interactive = sys.stdout.isatty()
+        self.footer_present = False
+        self.lastpids = []
+
+        if not self.interactive:
+            return
+
+        try:
+            import curses
+        except ImportError:
+            sys.exit("FATAL: The knotty ui could not load the required curses python module.")
+
+        import termios
+        self.curses = curses
+        self.termios = termios
+        try:
+            fd = sys.stdin.fileno()
+            self.stdinbackup = termios.tcgetattr(fd)
+            new = copy.deepcopy(self.stdinbackup)
+            new[3] = new[3] & ~termios.ECHO
+            termios.tcsetattr(fd, termios.TCSADRAIN, new)
+            curses.setupterm()
+            if curses.tigetnum("colors") > 2:
+                format.enable_color()
+            self.ed = curses.tigetstr("ed")
+            if self.ed:
+                self.cuu = curses.tigetstr("cuu")
+            try:
+                self._sigwinch_default = signal.getsignal(signal.SIGWINCH)
+                signal.signal(signal.SIGWINCH, self.sigwinch_handle)
+            except:
+                pass
+            self.columns = self.getTerminalColumns()
+        except:
+            self.cuu = None
+        console.addFilter(InteractConsoleLogFilter(self, format))
+        errconsole.addFilter(InteractConsoleLogFilter(self, format))
+
+    def clearFooter(self):
+        if self.footer_present:
+            lines = self.footer_present
+            sys.stdout.write(self.curses.tparm(self.cuu, lines))
+            sys.stdout.write(self.curses.tparm(self.ed))
+        self.footer_present = False
+
+    def updateFooter(self):
+        if not self.cuu:
+            return
+        activetasks = self.helper.running_tasks
+        failedtasks = self.helper.failed_tasks
+        runningpids = self.helper.running_pids
+        if self.footer_present and (self.lastcount == self.helper.tasknumber_current) and (self.lastpids == runningpids):
+            return
+        if self.footer_present:
+            self.clearFooter()
+        if (not self.helper.tasknumber_total or self.helper.tasknumber_current == self.helper.tasknumber_total) and not len(activetasks):
+            return
+        tasks = []
+        for t in runningpids:
+            tasks.append("%s (pid %s)" % (activetasks[t]["title"], t))
+
+        if self.main.shutdown:
+            content = "Waiting for %s running tasks to finish:" % len(activetasks)
+        elif not len(activetasks):
+            content = "No currently running tasks (%s of %s)" % (self.helper.tasknumber_current, self.helper.tasknumber_total)
+        else:
+            content = "Currently %s running tasks (%s of %s):" % (len(activetasks), self.helper.tasknumber_current, self.helper.tasknumber_total)
+        print(content)
+        lines = 1 + int(len(content) / (self.columns + 1))
+        for tasknum, task in enumerate(tasks):
+            content = "%s: %s" % (tasknum, task)
+            print(content)
+            lines = lines + 1 + int(len(content) / (self.columns + 1))
+        self.footer_present = lines
+        self.lastpids = runningpids[:]
+        self.lastcount = self.helper.tasknumber_current
+
+    def finish(self):
+        if self.stdinbackup:
+            fd = sys.stdin.fileno()
+            self.termios.tcsetattr(fd, self.termios.TCSADRAIN, self.stdinbackup)
+
+def _log_settings_from_server(server):
+    # Get values of variables which control our output
+    includelogs, error = server.runCommand(["getVariable", "BBINCLUDELOGS"])
+    if error:
+        logger.error("Unable to get the value of BBINCLUDELOGS variable: %s" % error)
+        raise BaseException(error)
+    loglines, error = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
+    if error:
+        logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s" % error)
+        raise BaseException(error)
+    consolelogfile, error = server.runCommand(["getVariable", "BB_CONSOLELOG"])
+    if error:
+        logger.error("Unable to get the value of BB_CONSOLELOG variable: %s" % error)
+        raise BaseException(error)
+    return includelogs, loglines, consolelogfile
+
+_evt_list = [ "bb.runqueue.runQueueExitWait", "bb.event.LogExecTTY", "logging.LogRecord",
+              "bb.build.TaskFailed", "bb.build.TaskBase", "bb.event.ParseStarted",
+              "bb.event.ParseProgress", "bb.event.ParseCompleted", "bb.event.CacheLoadStarted",
+              "bb.event.CacheLoadProgress", "bb.event.CacheLoadCompleted", "bb.command.CommandFailed",
+              "bb.command.CommandExit", "bb.command.CommandCompleted",  "bb.cooker.CookerExit",
+              "bb.event.MultipleProviders", "bb.event.NoProvider", "bb.runqueue.sceneQueueTaskStarted",
+              "bb.runqueue.runQueueTaskStarted", "bb.runqueue.runQueueTaskFailed", "bb.runqueue.sceneQueueTaskFailed",
+              "bb.event.BuildBase", "bb.build.TaskStarted", "bb.build.TaskSucceeded", "bb.build.TaskFailedSilent"]
+
+def main(server, eventHandler, params, tf = TerminalFilter):
+
+    includelogs, loglines, consolelogfile = _log_settings_from_server(server)
+
+    if sys.stdin.isatty() and sys.stdout.isatty():
+        log_exec_tty = True
+    else:
+        log_exec_tty = False
+
+    helper = uihelper.BBUIHelper()
+
+    console = logging.StreamHandler(sys.stdout)
+    errconsole = logging.StreamHandler(sys.stderr)
+    format_str = "%(levelname)s: %(message)s"
+    format = bb.msg.BBLogFormatter(format_str)
+    bb.msg.addDefaultlogFilter(console, bb.msg.BBLogFilterStdOut)
+    bb.msg.addDefaultlogFilter(errconsole, bb.msg.BBLogFilterStdErr)
+    console.setFormatter(format)
+    errconsole.setFormatter(format)
+    logger.addHandler(console)
+    logger.addHandler(errconsole)
+
+    if params.options.remote_server and params.options.kill_server:
+        server.terminateServer()
+        return
+
+    if consolelogfile and not params.options.show_environment and not params.options.show_versions:
+        bb.utils.mkdirhier(os.path.dirname(consolelogfile))
+        conlogformat = bb.msg.BBLogFormatter(format_str)
+        consolelog = logging.FileHandler(consolelogfile)
+        bb.msg.addDefaultlogFilter(consolelog)
+        consolelog.setFormatter(conlogformat)
+        logger.addHandler(consolelog)
+
+    llevel, debug_domains = bb.msg.constructLogOptions()
+    server.runCommand(["setEventMask", server.getEventHandle(), llevel, debug_domains, _evt_list])
+
+    if not params.observe_only:
+        params.updateFromServer(server)
+        params.updateToServer(server, os.environ.copy())
+        cmdline = params.parseActions()
+        if not cmdline:
+            print("Nothing to do.  Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
+            return 1
+        if 'msg' in cmdline and cmdline['msg']:
+            logger.error(cmdline['msg'])
+            return 1
+
+        ret, error = server.runCommand(cmdline['action'])
+        if error:
+            logger.error("Command '%s' failed: %s" % (cmdline, error))
+            return 1
+        elif ret != True:
+            logger.error("Command '%s' failed: returned %s" % (cmdline, ret))
+            return 1
+
+
+    parseprogress = None
+    cacheprogress = None
+    main.shutdown = 0
+    interrupted = False
+    return_value = 0
+    errors = 0
+    warnings = 0
+    taskfailures = []
+
+    termfilter = tf(main, helper, console, errconsole, format)
+    atexit.register(termfilter.finish)
+
+    while True:
+        try:
+            event = eventHandler.waitEvent(0)
+            if event is None:
+                if main.shutdown > 1:
+                    break
+                termfilter.updateFooter()
+                event = eventHandler.waitEvent(0.25)
+                if event is None:
+                    continue
+            helper.eventHandler(event)
+            if isinstance(event, bb.runqueue.runQueueExitWait):
+                if not main.shutdown:
+                    main.shutdown = 1
+                continue
+            if isinstance(event, bb.event.LogExecTTY):
+                if log_exec_tty:
+                    tries = event.retries
+                    while tries:
+                        print("Trying to run: %s" % event.prog)
+                        if os.system(event.prog) == 0:
+                            break
+                        time.sleep(event.sleep_delay)
+                        tries -= 1
+                    if tries:
+                        continue
+                logger.warn(event.msg)
+                continue
+
+            if isinstance(event, logging.LogRecord):
+                if event.levelno >= format.ERROR:
+                    errors = errors + 1
+                    return_value = 1
+                elif event.levelno == format.WARNING:
+                    warnings = warnings + 1
+                # For "normal" logging conditions, don't show note logs from tasks
+                # but do show them if the user has changed the default log level to
+                # include verbose/debug messages
+                if event.taskpid != 0 and event.levelno <= format.NOTE and (event.levelno < llevel or (event.levelno == format.NOTE and llevel != format.VERBOSE)):
+                    continue
+                logger.handle(event)
+                continue
+
+            if isinstance(event, bb.build.TaskFailedSilent):
+                logger.warn("Logfile for failed setscene task is %s" % event.logfile)
+                continue
+            if isinstance(event, bb.build.TaskFailed):
+                return_value = 1
+                logfile = event.logfile
+                if logfile and os.path.exists(logfile):
+                    termfilter.clearFooter()
+                    bb.error("Logfile of failure stored in: %s" % logfile)
+                    if includelogs and not event.errprinted:
+                        print("Log data follows:")
+                        f = open(logfile, "r")
+                        lines = []
+                        while True:
+                            l = f.readline()
+                            if l == '':
+                                break
+                            l = l.rstrip()
+                            if loglines:
+                                lines.append(' | %s' % l)
+                                if len(lines) > int(loglines):
+                                    lines.pop(0)
+                            else:
+                                print('| %s' % l)
+                        f.close()
+                        if lines:
+                            for line in lines:
+                                print(line)
+            if isinstance(event, bb.build.TaskBase):
+                logger.info(event._message)
+                continue
+            if isinstance(event, bb.event.ParseStarted):
+                if event.total == 0:
+                    continue
+                parseprogress = new_progress("Parsing recipes", event.total).start()
+                continue
+            if isinstance(event, bb.event.ParseProgress):
+                parseprogress.update(event.current)
+                continue
+            if isinstance(event, bb.event.ParseCompleted):
+                if not parseprogress:
+                    continue
+
+                parseprogress.finish()
+                print(("Parsing of %d .bb files complete (%d cached, %d parsed). %d targets, %d skipped, %d masked, %d errors."
+                    % ( event.total, event.cached, event.parsed, event.virtuals, event.skipped, event.masked, event.errors)))
+                continue
+
+            if isinstance(event, bb.event.CacheLoadStarted):
+                cacheprogress = new_progress("Loading cache", event.total).start()
+                continue
+            if isinstance(event, bb.event.CacheLoadProgress):
+                cacheprogress.update(event.current)
+                continue
+            if isinstance(event, bb.event.CacheLoadCompleted):
+                cacheprogress.finish()
+                print("Loaded %d entries from dependency cache." % event.num_entries)
+                continue
+
+            if isinstance(event, bb.command.CommandFailed):
+                return_value = event.exitcode
+                if event.error:
+                    errors = errors + 1
+                    logger.error("Command execution failed: %s", event.error)
+                main.shutdown = 2
+                continue
+            if isinstance(event, bb.command.CommandExit):
+                if not return_value:
+                    return_value = event.exitcode
+                continue
+            if isinstance(event, (bb.command.CommandCompleted, bb.cooker.CookerExit)):
+                main.shutdown = 2
+                continue
+            if isinstance(event, bb.event.MultipleProviders):
+                logger.info("multiple providers are available for %s%s (%s)", event._is_runtime and "runtime " or "",
+                            event._item,
+                            ", ".join(event._candidates))
+                logger.info("consider defining a PREFERRED_PROVIDER entry to match %s", event._item)
+                continue
+            if isinstance(event, bb.event.NoProvider):
+                return_value = 1
+                errors = errors + 1
+                if event._runtime:
+                    r = "R"
+                else:
+                    r = ""
+
+                extra = ''
+                if not event._reasons:
+                    if event._close_matches:
+                        extra = ". Close matches:\n  %s" % '\n  '.join(event._close_matches)
+
+                if event._dependees:
+                    logger.error("Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s", r, event._item, ", ".join(event._dependees), r, extra)
+                else:
+                    logger.error("Nothing %sPROVIDES '%s'%s", r, event._item, extra)
+                if event._reasons:
+                    for reason in event._reasons:
+                        logger.error("%s", reason)
+                continue
+
+            if isinstance(event, bb.runqueue.sceneQueueTaskStarted):
+                logger.info("Running setscene task %d of %d (%s)" % (event.stats.completed + event.stats.active + event.stats.failed + 1, event.stats.total, event.taskstring))
+                continue
+
+            if isinstance(event, bb.runqueue.runQueueTaskStarted):
+                if event.noexec:
+                    tasktype = 'noexec task'
+                else:
+                    tasktype = 'task'
+                logger.info("Running %s %s of %s (ID: %s, %s)",
+                            tasktype,
+                            event.stats.completed + event.stats.active +
+                                event.stats.failed + 1,
+                            event.stats.total, event.taskid, event.taskstring)
+                continue
+
+            if isinstance(event, bb.runqueue.runQueueTaskFailed):
+                taskfailures.append(event.taskstring)
+                logger.error("Task %s (%s) failed with exit code '%s'",
+                             event.taskid, event.taskstring, event.exitcode)
+                continue
+
+            if isinstance(event, bb.runqueue.sceneQueueTaskFailed):
+                logger.warn("Setscene task %s (%s) failed with exit code '%s' - real task will be run instead",
+                             event.taskid, event.taskstring, event.exitcode)
+                continue
+
+            if isinstance(event, bb.event.DepTreeGenerated):
+                continue
+
+            # ignore
+            if isinstance(event, (bb.event.BuildBase,
+                                  bb.event.MetadataEvent,
+                                  bb.event.StampUpdate,
+                                  bb.event.ConfigParsed,
+                                  bb.event.RecipeParsed,
+                                  bb.event.RecipePreFinalise,
+                                  bb.runqueue.runQueueEvent,
+                                  bb.event.OperationStarted,
+                                  bb.event.OperationCompleted,
+                                  bb.event.OperationProgress,
+                                  bb.event.DiskFull)):
+                continue
+
+            logger.error("Unknown event: %s", event)
+
+        except EnvironmentError as ioerror:
+            termfilter.clearFooter()
+            # ignore interrupted io
+            if ioerror.args[0] == 4:
+                continue
+            sys.stderr.write(str(ioerror))
+            if not params.observe_only:
+                _, error = server.runCommand(["stateForceShutdown"])
+            main.shutdown = 2
+        except KeyboardInterrupt:
+            termfilter.clearFooter()
+            if params.observe_only:
+                print("\nKeyboard Interrupt, exiting observer...")
+                main.shutdown = 2
+            if not params.observe_only and main.shutdown == 1:
+                print("\nSecond Keyboard Interrupt, stopping...\n")
+                _, error = server.runCommand(["stateForceShutdown"])
+                if error:
+                    logger.error("Unable to cleanly stop: %s" % error)
+            if not params.observe_only and main.shutdown == 0:
+                print("\nKeyboard Interrupt, closing down...\n")
+                interrupted = True
+                _, error = server.runCommand(["stateShutdown"])
+                if error:
+                    logger.error("Unable to cleanly shutdown: %s" % error)
+            main.shutdown = main.shutdown + 1
+            pass
+        except Exception as e:
+            sys.stderr.write(str(e))
+            if not params.observe_only:
+                _, error = server.runCommand(["stateForceShutdown"])
+            main.shutdown = 2
+    try:
+        summary = ""
+        if taskfailures:
+            summary += pluralise("\nSummary: %s task failed:",
+                                 "\nSummary: %s tasks failed:", len(taskfailures))
+            for failure in taskfailures:
+                summary += "\n  %s" % failure
+        if warnings:
+            summary += pluralise("\nSummary: There was %s WARNING message shown.",
+                                 "\nSummary: There were %s WARNING messages shown.", warnings)
+        if return_value and errors:
+            summary += pluralise("\nSummary: There was %s ERROR message shown, returning a non-zero exit code.",
+                                 "\nSummary: There were %s ERROR messages shown, returning a non-zero exit code.", errors)
+        if summary:
+            print(summary)
+
+        if interrupted:
+            print("Execution was interrupted, returning a non-zero exit code.")
+            if return_value == 0:
+                return_value = 1
+    except IOError as e:
+        import errno
+        if e.errno == errno.EPIPE:
+            pass
+
+    return return_value
diff --git a/bitbake/lib/bb/ui/ncurses.py b/bitbake/lib/bb/ui/ncurses.py
new file mode 100644
index 0000000..9589a77
--- /dev/null
+++ b/bitbake/lib/bb/ui/ncurses.py
@@ -0,0 +1,373 @@
+#
+# BitBake Curses UI Implementation
+#
+# Implements an ncurses frontend for the BitBake utility.
+#
+# Copyright (C) 2006 Michael 'Mickey' Lauer
+# Copyright (C) 2006-2007 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+    We have the following windows:
+
+        1.) Main Window: Shows what we are ultimately building and how far we are. Includes status bar
+        2.) Thread Activity Window: Shows one status line for every concurrent bitbake thread.
+        3.) Command Line Window: Contains an interactive command line where you can interact w/ Bitbake.
+
+    Basic window layout is like that:
+
+        |---------------------------------------------------------|
+        | <Main Window>               | <Thread Activity Window>  |
+        |                             | 0: foo do_compile complete|
+        | Building Gtk+-2.6.10        | 1: bar do_patch complete  |
+        | Status: 60%                 | ...                       |
+        |                             | ...                       |
+        |                             | ...                       |
+        |---------------------------------------------------------|
+        |<Command Line Window>                                    |
+        |>>> which virtual/kernel                                 |
+        |openzaurus-kernel                                        |
+        |>>> _                                                    |
+        |---------------------------------------------------------|
+
+"""
+
+
+from __future__ import division
+import logging
+import os, sys, itertools, time, subprocess
+
+try:
+    import curses
+except ImportError:
+    sys.exit("FATAL: The ncurses ui could not load the required curses python module.")
+
+import bb
+import xmlrpclib
+from bb import ui
+from bb.ui import uihelper
+
+parsespin = itertools.cycle( r'|/-\\' )
+
+X = 0
+Y = 1
+WIDTH = 2
+HEIGHT = 3
+
+MAXSTATUSLENGTH = 32
+
+class NCursesUI:
+    """
+    NCurses UI Class
+    """
+    class Window:
+        """Base Window Class"""
+        def __init__( self, x, y, width, height, fg=curses.COLOR_BLACK, bg=curses.COLOR_WHITE ):
+            self.win = curses.newwin( height, width, y, x )
+            self.dimensions = ( x, y, width, height )
+            """
+            if curses.has_colors():
+                color = 1
+                curses.init_pair( color, fg, bg )
+                self.win.bkgdset( ord(' '), curses.color_pair(color) )
+            else:
+                self.win.bkgdset( ord(' '), curses.A_BOLD )
+            """
+            self.erase()
+            self.setScrolling()
+            self.win.noutrefresh()
+
+        def erase( self ):
+            self.win.erase()
+
+        def setScrolling( self, b = True ):
+            self.win.scrollok( b )
+            self.win.idlok( b )
+
+        def setBoxed( self ):
+            self.boxed = True
+            self.win.box()
+            self.win.noutrefresh()
+
+        def setText( self, x, y, text, *args ):
+            self.win.addstr( y, x, text, *args )
+            self.win.noutrefresh()
+
+        def appendText( self, text, *args ):
+            self.win.addstr( text, *args )
+            self.win.noutrefresh()
+
+        def drawHline( self, y ):
+            self.win.hline( y, 0, curses.ACS_HLINE, self.dimensions[WIDTH] )
+            self.win.noutrefresh()
+
+    class DecoratedWindow( Window ):
+        """Base class for windows with a box and a title bar"""
+        def __init__( self, title, x, y, width, height, fg=curses.COLOR_BLACK, bg=curses.COLOR_WHITE ):
+            NCursesUI.Window.__init__( self, x+1, y+3, width-2, height-4, fg, bg )
+            self.decoration = NCursesUI.Window( x, y, width, height, fg, bg )
+            self.decoration.setBoxed()
+            self.decoration.win.hline( 2, 1, curses.ACS_HLINE, width-2 )
+            self.setTitle( title )
+
+        def setTitle( self, title ):
+            self.decoration.setText( 1, 1, title.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD )
+
+    #-------------------------------------------------------------------------#
+#    class TitleWindow( Window ):
+    #-------------------------------------------------------------------------#
+#        """Title Window"""
+#        def __init__( self, x, y, width, height ):
+#            NCursesUI.Window.__init__( self, x, y, width, height )
+#            version = bb.__version__
+#            title = "BitBake %s" % version
+#            credit = "(C) 2003-2007 Team BitBake"
+#            #self.win.hline( 2, 1, curses.ACS_HLINE, width-2 )
+#            self.win.border()
+#            self.setText( 1, 1, title.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD )
+#            self.setText( 1, 2, credit.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD )
+
+    #-------------------------------------------------------------------------#
+    class ThreadActivityWindow( DecoratedWindow ):
+    #-------------------------------------------------------------------------#
+        """Thread Activity Window"""
+        def __init__( self, x, y, width, height ):
+            NCursesUI.DecoratedWindow.__init__( self, "Thread Activity", x, y, width, height )
+
+        def setStatus( self, thread, text ):
+            line = "%02d: %s" % ( thread, text )
+            width = self.dimensions[WIDTH]
+            if ( len(line) > width ):
+                line = line[:width-3] + "..."
+            else:
+                line = line.ljust( width )
+            self.setText( 0, thread, line )
+
+    #-------------------------------------------------------------------------#
+    class MainWindow( DecoratedWindow ):
+    #-------------------------------------------------------------------------#
+        """Main Window"""
+        def __init__( self, x, y, width, height ):
+            self.StatusPosition = width - MAXSTATUSLENGTH
+            NCursesUI.DecoratedWindow.__init__( self, None, x, y, width, height )
+            curses.nl()
+
+        def setTitle( self, title ):
+            title = "BitBake %s" % bb.__version__
+            self.decoration.setText( 2, 1, title, curses.A_BOLD )
+            self.decoration.setText( self.StatusPosition - 8, 1, "Status:", curses.A_BOLD )
+
+        def setStatus(self, status):
+            while len(status) < MAXSTATUSLENGTH:
+                status = status + " "
+            self.decoration.setText( self.StatusPosition, 1, status, curses.A_BOLD )
+
+
+    #-------------------------------------------------------------------------#
+    class ShellOutputWindow( DecoratedWindow ):
+    #-------------------------------------------------------------------------#
+        """Interactive Command Line Output"""
+        def __init__( self, x, y, width, height ):
+            NCursesUI.DecoratedWindow.__init__( self, "Command Line Window", x, y, width, height )
+
+    #-------------------------------------------------------------------------#
+    class ShellInputWindow( Window ):
+    #-------------------------------------------------------------------------#
+        """Interactive Command Line Input"""
+        def __init__( self, x, y, width, height ):
+            NCursesUI.Window.__init__( self, x, y, width, height )
+
+# put that to the top again from curses.textpad import Textbox
+#            self.textbox = Textbox( self.win )
+#            t = threading.Thread()
+#            t.run = self.textbox.edit
+#            t.start()
+
+    #-------------------------------------------------------------------------#
+    def main(self, stdscr, server, eventHandler, params):
+    #-------------------------------------------------------------------------#
+        height, width = stdscr.getmaxyx()
+
+        # for now split it like that:
+        # MAIN_y + THREAD_y = 2/3 screen at the top
+        # MAIN_x = 2/3 left, THREAD_y = 1/3 right
+        # CLI_y = 1/3 of screen at the bottom
+        # CLI_x = full
+
+        main_left = 0
+        main_top = 0
+        main_height = ( height // 3 * 2 )
+        main_width = ( width // 3 ) * 2
+        clo_left = main_left
+        clo_top = main_top + main_height
+        clo_height = height - main_height - main_top - 1
+        clo_width = width
+        cli_left = main_left
+        cli_top = clo_top + clo_height
+        cli_height = 1
+        cli_width = width
+        thread_left = main_left + main_width
+        thread_top = main_top
+        thread_height = main_height
+        thread_width = width - main_width
+
+        #tw = self.TitleWindow( 0, 0, width, main_top )
+        mw = self.MainWindow( main_left, main_top, main_width, main_height )
+        taw = self.ThreadActivityWindow( thread_left, thread_top, thread_width, thread_height )
+        clo = self.ShellOutputWindow( clo_left, clo_top, clo_width, clo_height )
+        cli = self.ShellInputWindow( cli_left, cli_top, cli_width, cli_height )
+        cli.setText( 0, 0, "BB>" )
+
+        mw.setStatus("Idle")
+
+        helper = uihelper.BBUIHelper()
+        shutdown = 0
+
+        try:
+            params.updateFromServer(server)
+            cmdline = params.parseActions()
+            if not cmdline:
+                print("Nothing to do.  Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
+                return 1
+            if 'msg' in cmdline and cmdline['msg']:
+                logger.error(cmdline['msg'])
+                return 1
+            cmdline = cmdline['action']
+            ret, error = server.runCommand(cmdline)
+            if error:
+                print("Error running command '%s': %s" % (cmdline, error))
+                return
+            elif ret != True:
+                print("Couldn't get default commandlind! %s" % ret)
+                return
+        except xmlrpclib.Fault as x:
+            print("XMLRPC Fault getting commandline:\n %s" % x)
+            return
+
+        exitflag = False
+        while not exitflag:
+            try:
+                event = eventHandler.waitEvent(0.25)
+                if not event:
+                    continue
+
+                helper.eventHandler(event)
+                if isinstance(event, bb.build.TaskBase):
+                    mw.appendText("NOTE: %s\n" % event._message)
+                if isinstance(event, logging.LogRecord):
+                    mw.appendText(logging.getLevelName(event.levelno) + ': ' + event.getMessage() + '\n')
+
+                if isinstance(event, bb.event.CacheLoadStarted):
+                    self.parse_total = event.total
+                if isinstance(event, bb.event.CacheLoadProgress):
+                    x = event.current
+                    y = self.parse_total
+                    mw.setStatus("Loading Cache:   %s [%2d %%]" % ( next(parsespin), x*100/y ) )
+                if isinstance(event, bb.event.CacheLoadCompleted):
+                    mw.setStatus("Idle")
+                    mw.appendText("Loaded %d entries from dependency cache.\n"
+                                % ( event.num_entries))
+
+                if isinstance(event, bb.event.ParseStarted):
+                    self.parse_total = event.total
+                if isinstance(event, bb.event.ParseProgress):
+                    x = event.current
+                    y = self.parse_total
+                    mw.setStatus("Parsing Recipes: %s [%2d %%]" % ( next(parsespin), x*100/y ) )
+                if isinstance(event, bb.event.ParseCompleted):
+                    mw.setStatus("Idle")
+                    mw.appendText("Parsing finished. %d cached, %d parsed, %d skipped, %d masked.\n"
+                                % ( event.cached, event.parsed, event.skipped, event.masked ))
+
+#                if isinstance(event, bb.build.TaskFailed):
+#                    if event.logfile:
+#                        if data.getVar("BBINCLUDELOGS", d):
+#                            bb.error("log data follows (%s)" % logfile)
+#                            number_of_lines = data.getVar("BBINCLUDELOGS_LINES", d)
+#                            if number_of_lines:
+#                                subprocess.call('tail -n%s %s' % (number_of_lines, logfile), shell=True)
+#                            else:
+#                                f = open(logfile, "r")
+#                                while True:
+#                                    l = f.readline()
+#                                    if l == '':
+#                                        break
+#                                    l = l.rstrip()
+#                                    print '| %s' % l
+#                                f.close()
+#                        else:
+#                            bb.error("see log in %s" % logfile)
+
+                if isinstance(event, bb.command.CommandCompleted):
+                    # stop so the user can see the result of the build, but
+                    # also allow them to now exit with a single ^C
+                    shutdown = 2
+                if isinstance(event, bb.command.CommandFailed):
+                    mw.appendText("Command execution failed: %s" % event.error)
+                    time.sleep(2)
+                    exitflag = True
+                if isinstance(event, bb.command.CommandExit):
+                    exitflag = True
+                if isinstance(event, bb.cooker.CookerExit):
+                    exitflag = True
+
+                if isinstance(event, bb.event.LogExecTTY):
+                    mw.appendText('WARN: ' + event.msg + '\n')
+                if helper.needUpdate:
+                    activetasks, failedtasks = helper.getTasks()
+                    taw.erase()
+                    taw.setText(0, 0, "")
+                    if activetasks:
+                        taw.appendText("Active Tasks:\n")
+                        for task in activetasks.itervalues():
+                            taw.appendText(task["title"] + '\n')
+                    if failedtasks:
+                        taw.appendText("Failed Tasks:\n")
+                        for task in failedtasks:
+                            taw.appendText(task["title"] + '\n')
+
+                curses.doupdate()
+            except EnvironmentError as ioerror:
+                # ignore interrupted io
+                if ioerror.args[0] == 4:
+                    pass
+
+            except KeyboardInterrupt:
+                if shutdown == 2:
+                    mw.appendText("Third Keyboard Interrupt, exit.\n")
+                    exitflag = True
+                if shutdown == 1:
+                    mw.appendText("Second Keyboard Interrupt, stopping...\n")
+                    _, error = server.runCommand(["stateForceShutdown"])
+                    if error:
+                        print("Unable to cleanly stop: %s" % error)
+                if shutdown == 0:
+                    mw.appendText("Keyboard Interrupt, closing down...\n")
+                    _, error = server.runCommand(["stateShutdown"])
+                    if error:
+                        print("Unable to cleanly shutdown: %s" % error)
+                shutdown = shutdown + 1
+                pass
+
+def main(server, eventHandler, params):
+    if not os.isatty(sys.stdout.fileno()):
+        print("FATAL: Unable to run 'ncurses' UI without a TTY.")
+        return
+    ui = NCursesUI()
+    try:
+        curses.wrapper(ui.main, server, eventHandler, params)
+    except:
+        import traceback
+        traceback.print_exc()
diff --git a/bitbake/lib/bb/ui/puccho.py b/bitbake/lib/bb/ui/puccho.py
new file mode 100644
index 0000000..3ce4590
--- /dev/null
+++ b/bitbake/lib/bb/ui/puccho.py
@@ -0,0 +1,425 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2008        Intel Corporation
+#
+# Authored by Rob Bradford <rob@linux.intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import gobject
+import gtk.glade
+import threading
+import urllib2
+import os
+import contextlib
+
+from bb.ui.crumbs.buildmanager import BuildManager, BuildConfiguration
+from bb.ui.crumbs.buildmanager import BuildManagerTreeView
+
+from bb.ui.crumbs.runningbuild import RunningBuild, RunningBuildTreeView
+
+# The metadata loader is used by the BuildSetupDialog to download the
+# available options to populate the dialog
+class MetaDataLoader(gobject.GObject):
+    """ This class provides the mechanism for loading the metadata (the
+    fetching and parsing) from a given URL. The metadata encompasses details
+    on what machines are available. The distribution and images available for
+    the machine and the the uris to use for building the given machine."""
+    __gsignals__ = {
+        'success' : (gobject.SIGNAL_RUN_LAST,
+                     gobject.TYPE_NONE,
+                     ()),
+        'error' : (gobject.SIGNAL_RUN_LAST,
+                   gobject.TYPE_NONE,
+                   (gobject.TYPE_STRING,))
+        }
+
+    # We use these little helper functions to ensure that we take the gdk lock
+    # when emitting the signal. These functions are called as idles (so that
+    # they happen in the gtk / main thread's main loop.
+    def emit_error_signal (self, remark):
+        gtk.gdk.threads_enter()
+        self.emit ("error", remark)
+        gtk.gdk.threads_leave()
+
+    def emit_success_signal (self):
+        gtk.gdk.threads_enter()
+        self.emit ("success")
+        gtk.gdk.threads_leave()
+
+    def __init__ (self):
+        gobject.GObject.__init__ (self)
+
+    class LoaderThread(threading.Thread):
+        """ This class provides an asynchronous loader for the metadata (by
+        using threads and signals). This is useful since the metadata may be
+        at a remote URL."""
+        class LoaderImportException (Exception):
+            pass
+
+        def __init__(self, loader, url):
+            threading.Thread.__init__ (self)
+            self.url = url
+            self.loader = loader
+
+        def run (self):
+            result = {}
+            try:
+                with contextlib.closing (urllib2.urlopen (self.url)) as f:
+                    # Parse the metadata format. The format is....
+                    # <machine>;<default distro>|<distro>...;<default image>|<image>...;<type##url>|...
+                    for line in f:
+                        components = line.split(";")
+                        if (len (components) < 4):
+                            raise MetaDataLoader.LoaderThread.LoaderImportException
+                        machine = components[0]
+                        distros = components[1].split("|")
+                        images = components[2].split("|")
+                        urls = components[3].split("|")
+
+                        result[machine] = (distros, images, urls)
+
+                # Create an object representing this *potential*
+                # configuration. It can become concrete if the machine, distro
+                # and image are all chosen in the UI
+                configuration = BuildConfiguration()
+                configuration.metadata_url = self.url
+                configuration.machine_options = result
+                self.loader.configuration = configuration
+
+                # Emit that we've actually got a configuration
+                gobject.idle_add (MetaDataLoader.emit_success_signal,
+                    self.loader)
+
+            except MetaDataLoader.LoaderThread.LoaderImportException as e:
+                gobject.idle_add (MetaDataLoader.emit_error_signal, self.loader,
+                    "Repository metadata corrupt")
+            except Exception as e:
+                gobject.idle_add (MetaDataLoader.emit_error_signal, self.loader,
+                    "Unable to download repository metadata")
+                print(e)
+
+    def try_fetch_from_url (self, url):
+        # Try and download the metadata. Firing a signal if successful
+        thread = MetaDataLoader.LoaderThread(self, url)
+        thread.start()
+
+class BuildSetupDialog (gtk.Dialog):
+    RESPONSE_BUILD = 1
+
+    # A little helper method that just sets the states on the widgets based on
+    # whether we've got good metadata or not.
+    def set_configurable (self, configurable):
+        if (self.configurable == configurable):
+            return
+
+        self.configurable = configurable
+        for widget in self.conf_widgets:
+            widget.set_sensitive (configurable)
+
+        if not configurable:
+            self.machine_combo.set_active (-1)
+            self.distribution_combo.set_active (-1)
+            self.image_combo.set_active (-1)
+
+    # GTK widget callbacks
+    def refresh_button_clicked (self, button):
+        # Refresh button clicked.
+
+        url = self.location_entry.get_chars (0, -1)
+        self.loader.try_fetch_from_url(url)
+
+    def repository_entry_editable_changed (self, entry):
+        if (len (entry.get_chars (0, -1)) > 0):
+            self.refresh_button.set_sensitive (True)
+        else:
+            self.refresh_button.set_sensitive (False)
+            self.clear_status_message()
+
+        # If we were previously configurable we are no longer since the
+        # location entry has been changed
+        self.set_configurable (False)
+
+    def machine_combo_changed (self, combobox):
+        active_iter = combobox.get_active_iter()
+
+        if not active_iter:
+            return
+
+        model = combobox.get_model()
+
+        if model:
+            chosen_machine = model.get (active_iter, 0)[0]
+
+        (distros_model, images_model) = \
+            self.loader.configuration.get_distro_and_images_models (chosen_machine)
+
+        self.distribution_combo.set_model (distros_model)
+        self.image_combo.set_model (images_model)
+
+    # Callbacks from the loader
+    def loader_success_cb (self, loader):
+        self.status_image.set_from_icon_name ("info",
+            gtk.ICON_SIZE_BUTTON)
+        self.status_image.show()
+        self.status_label.set_label ("Repository metadata successfully downloaded")
+
+        # Set the models on the combo boxes based on the models generated from
+        # the configuration that the loader has created
+
+        # We just need to set the machine here, that then determines the
+        # distro and image options. Cunning huh? :-)
+
+        self.configuration = self.loader.configuration
+        model = self.configuration.get_machines_model ()
+        self.machine_combo.set_model (model)
+
+        self.set_configurable (True)
+
+    def loader_error_cb (self, loader, message):
+        self.status_image.set_from_icon_name ("error",
+            gtk.ICON_SIZE_BUTTON)
+        self.status_image.show()
+        self.status_label.set_text ("Error downloading repository metadata")
+        for widget in self.conf_widgets:
+            widget.set_sensitive (False)
+
+    def clear_status_message (self):
+        self.status_image.hide()
+        self.status_label.set_label (
+            """<i>Enter the repository location and press _Refresh</i>""")
+
+    def __init__ (self):
+        gtk.Dialog.__init__ (self)
+
+        # Cancel
+        self.add_button (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
+
+        # Build
+        button = gtk.Button ("_Build", None, True)
+        image = gtk.Image ()
+        image.set_from_stock (gtk.STOCK_EXECUTE, gtk.ICON_SIZE_BUTTON)
+        button.set_image (image)
+        self.add_action_widget (button, BuildSetupDialog.RESPONSE_BUILD)
+        button.show_all ()
+
+        # Pull in *just* the table from the Glade XML data.
+        gxml = gtk.glade.XML (os.path.dirname(__file__) + "/crumbs/puccho.glade",
+            root = "build_table")
+        table = gxml.get_widget ("build_table")
+        self.vbox.pack_start (table, True, False, 0)
+
+        # Grab all the widgets that we need to turn on/off when we refresh...
+        self.conf_widgets = []
+        self.conf_widgets += [gxml.get_widget ("machine_label")]
+        self.conf_widgets += [gxml.get_widget ("distribution_label")]
+        self.conf_widgets += [gxml.get_widget ("image_label")]
+        self.conf_widgets += [gxml.get_widget ("machine_combo")]
+        self.conf_widgets += [gxml.get_widget ("distribution_combo")]
+        self.conf_widgets += [gxml.get_widget ("image_combo")]
+
+        # Grab the status widgets
+        self.status_image = gxml.get_widget ("status_image")
+        self.status_label = gxml.get_widget ("status_label")
+
+        # Grab the refresh button and connect to the clicked signal
+        self.refresh_button = gxml.get_widget ("refresh_button")
+        self.refresh_button.connect ("clicked", self.refresh_button_clicked)
+
+        # Grab the location entry and connect to editable::changed
+        self.location_entry = gxml.get_widget ("location_entry")
+        self.location_entry.connect ("changed",
+            self.repository_entry_editable_changed)
+
+        # Grab the machine combo and hook onto the changed signal. This then
+        # allows us to populate the distro and image combos
+        self.machine_combo = gxml.get_widget ("machine_combo")
+        self.machine_combo.connect ("changed", self.machine_combo_changed)
+
+        # Setup the combo
+        cell = gtk.CellRendererText()
+        self.machine_combo.pack_start(cell, True)
+        self.machine_combo.add_attribute(cell, 'text', 0)
+
+        # Grab the distro and image combos. We need these to populate with
+        # models once the machine is chosen
+        self.distribution_combo = gxml.get_widget ("distribution_combo")
+        cell = gtk.CellRendererText()
+        self.distribution_combo.pack_start(cell, True)
+        self.distribution_combo.add_attribute(cell, 'text', 0)
+
+        self.image_combo = gxml.get_widget ("image_combo")
+        cell = gtk.CellRendererText()
+        self.image_combo.pack_start(cell, True)
+        self.image_combo.add_attribute(cell, 'text', 0)
+
+        # Put the default descriptive text in the status box
+        self.clear_status_message()
+
+        # Mark as non-configurable, this is just greys out the widgets the
+        # user can't yet use
+        self.configurable = False
+        self.set_configurable(False)
+
+        # Show the table
+        table.show_all ()
+
+        # The loader and some signals connected to it to update the status
+        # area
+        self.loader = MetaDataLoader()
+        self.loader.connect ("success", self.loader_success_cb)
+        self.loader.connect ("error", self.loader_error_cb)
+
+    def update_configuration (self):
+        """ A poorly named function but it updates the internal configuration
+        from the widgets. This can make that configuration concrete and can
+        thus be used for building """
+        # Extract the chosen machine from the combo
+        model = self.machine_combo.get_model()
+        active_iter = self.machine_combo.get_active_iter()
+        if (active_iter):
+            self.configuration.machine = model.get(active_iter, 0)[0]
+
+        # Extract the chosen distro from the combo
+        model = self.distribution_combo.get_model()
+        active_iter = self.distribution_combo.get_active_iter()
+        if (active_iter):
+            self.configuration.distro = model.get(active_iter, 0)[0]
+
+        # Extract the chosen image from the combo
+        model = self.image_combo.get_model()
+        active_iter = self.image_combo.get_active_iter()
+        if (active_iter):
+            self.configuration.image = model.get(active_iter, 0)[0]
+
+# This function operates to pull events out from the event queue and then push
+# them into the RunningBuild (which then drives the RunningBuild which then
+# pushes through and updates the progress tree view.)
+#
+# TODO: Should be a method on the RunningBuild class
+def event_handle_timeout (eventHandler, build):
+    # Consume as many messages as we can ...
+    event = eventHandler.getEvent()
+    while event:
+        build.handle_event (event)
+        event = eventHandler.getEvent()
+    return True
+
+class MainWindow (gtk.Window):
+
+    # Callback that gets fired when the user hits a button in the
+    # BuildSetupDialog.
+    def build_dialog_box_response_cb (self, dialog, response_id):
+        conf = None
+        if (response_id == BuildSetupDialog.RESPONSE_BUILD):
+            dialog.update_configuration()
+            print(dialog.configuration.machine, dialog.configuration.distro, \
+                dialog.configuration.image)
+            conf = dialog.configuration
+
+        dialog.destroy()
+
+        if conf:
+            self.manager.do_build (conf)
+
+    def build_button_clicked_cb (self, button):
+        dialog = BuildSetupDialog ()
+
+        # For some unknown reason Dialog.run causes nice little deadlocks ... :-(
+        dialog.connect ("response", self.build_dialog_box_response_cb)
+        dialog.show()
+
+    def __init__ (self):
+        gtk.Window.__init__ (self)
+
+        # Pull in *just* the main vbox from the Glade XML data and then pack
+        # that inside the window
+        gxml = gtk.glade.XML (os.path.dirname(__file__) + "/crumbs/puccho.glade",
+            root = "main_window_vbox")
+        vbox = gxml.get_widget ("main_window_vbox")
+        self.add (vbox)
+
+        # Create the tree views for the build manager view and the progress view
+        self.build_manager_view = BuildManagerTreeView()
+        self.running_build_view = RunningBuildTreeView()
+
+        # Grab the scrolled windows that we put the tree views into
+        self.results_scrolledwindow = gxml.get_widget ("results_scrolledwindow")
+        self.progress_scrolledwindow = gxml.get_widget ("progress_scrolledwindow")
+
+        # Put the tree views inside ...
+        self.results_scrolledwindow.add (self.build_manager_view)
+        self.progress_scrolledwindow.add (self.running_build_view)
+
+        # Hook up the build button...
+        self.build_button = gxml.get_widget ("main_toolbutton_build")
+        self.build_button.connect ("clicked", self.build_button_clicked_cb)
+
+# I'm not very happy about the current ownership of the RunningBuild. I have
+# my suspicions that this object should be held by the BuildManager since we
+# care about the signals in the manager
+
+def running_build_succeeded_cb (running_build, manager):
+    # Notify the manager that a build has succeeded. This is necessary as part
+    # of the 'hack' that we use for making the row in the model / view
+    # representing the ongoing build change into a row representing the
+    # completed build. Since we know only one build can be running a time then
+    # we can handle this.
+
+    # FIXME: Refactor all this so that the RunningBuild is owned by the
+    # BuildManager. It can then hook onto the signals directly and drive
+    # interesting things it cares about.
+    manager.notify_build_succeeded ()
+    print("build succeeded")
+
+def running_build_failed_cb (running_build, manager):
+    # As above
+    print("build failed")
+    manager.notify_build_failed ()
+
+def main (server, eventHandler):
+    # Initialise threading...
+    gobject.threads_init()
+    gtk.gdk.threads_init()
+
+    main_window = MainWindow ()
+    main_window.show_all ()
+
+    # Set up the build manager stuff in general
+    builds_dir = os.path.join (os.getcwd(),  "results")
+    manager = BuildManager (server, builds_dir)
+    main_window.build_manager_view.set_model (manager.model)
+
+    # Do the running build setup
+    running_build = RunningBuild ()
+    main_window.running_build_view.set_model (running_build.model)
+    running_build.connect ("build-succeeded", running_build_succeeded_cb,
+        manager)
+    running_build.connect ("build-failed", running_build_failed_cb, manager)
+
+    # We need to save the manager into the MainWindow so that the toolbar
+    # button can use it.
+    # FIXME: Refactor ?
+    main_window.manager = manager
+
+    # Use a timeout function for probing the event queue to find out if we
+    # have a message waiting for us.
+    gobject.timeout_add (200,
+                         event_handle_timeout,
+                         eventHandler,
+                         running_build)
+
+    gtk.main()
diff --git a/bitbake/lib/bb/ui/toasterui.py b/bitbake/lib/bb/ui/toasterui.py
new file mode 100644
index 0000000..9c7e87d
--- /dev/null
+++ b/bitbake/lib/bb/ui/toasterui.py
@@ -0,0 +1,355 @@
+#
+# BitBake ToasterUI Implementation
+# based on (No)TTY UI Implementation by Richard Purdie
+#
+# Handling output to TTYs or files (no TTY)
+#
+# Copyright (C) 2006-2012 Richard Purdie
+# Copyright (C) 2013      Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from __future__ import division
+import sys
+try:
+    import bb
+except RuntimeError as exc:
+    sys.exit(str(exc))
+
+from bb.ui import uihelper
+from bb.ui.buildinfohelper import BuildInfoHelper
+
+import bb.msg
+import logging
+import os
+
+# pylint: disable=invalid-name
+# module properties for UI modules are read by bitbake and the contract should not be broken
+
+
+featureSet = [bb.cooker.CookerFeatures.HOB_EXTRA_CACHES, bb.cooker.CookerFeatures.SEND_DEPENDS_TREE, bb.cooker.CookerFeatures.BASEDATASTORE_TRACKING, bb.cooker.CookerFeatures.SEND_SANITYEVENTS]
+
+logger = logging.getLogger("ToasterLogger")
+interactive = sys.stdout.isatty()
+
+
+
+def _log_settings_from_server(server):
+    # Get values of variables which control our output
+    includelogs, error = server.runCommand(["getVariable", "BBINCLUDELOGS"])
+    if error:
+        logger.error("Unable to get the value of BBINCLUDELOGS variable: %s", error)
+        raise BaseException(error)
+    loglines, error = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
+    if error:
+        logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s", error)
+        raise BaseException(error)
+    consolelogfile, error = server.runCommand(["getVariable", "BB_CONSOLELOG"])
+    if error:
+        logger.error("Unable to get the value of BB_CONSOLELOG variable: %s", error)
+        raise BaseException(error)
+    return includelogs, loglines, consolelogfile
+
+
+def main(server, eventHandler, params ):
+    helper = uihelper.BBUIHelper()
+
+    console = logging.StreamHandler(sys.stdout)
+    format_str = "%(levelname)s: %(message)s"
+    formatter = bb.msg.BBLogFormatter(format_str)
+    bb.msg.addDefaultlogFilter(console)
+    console.setFormatter(formatter)
+    logger.addHandler(console)
+    logger.setLevel(logging.INFO)
+
+    _, _, consolelogfile = _log_settings_from_server(server)
+
+    # verify and warn
+    build_history_enabled = True
+    inheritlist, _ = server.runCommand(["getVariable", "INHERIT"])
+
+    if not "buildhistory" in inheritlist.split(" "):
+        logger.warn("buildhistory is not enabled. Please enable INHERIT += \"buildhistory\" to see image details.")
+        build_history_enabled = False
+
+    if not params.observe_only:
+        logger.error("ToasterUI can only work in observer mode")
+        return 1
+
+
+    main.shutdown = 0
+    interrupted = False
+    return_value = 0
+    errors = 0
+    warnings = 0
+    taskfailures = []
+    first = True
+
+    buildinfohelper = BuildInfoHelper(server, build_history_enabled)
+
+    if buildinfohelper.brbe is not None and consolelogfile:
+        # if we are under managed mode we have no other UI and we need to write our own file
+        bb.utils.mkdirhier(os.path.dirname(consolelogfile))
+        conlogformat = bb.msg.BBLogFormatter(format_str)
+        consolelog = logging.FileHandler(consolelogfile)
+        bb.msg.addDefaultlogFilter(consolelog)
+        consolelog.setFormatter(conlogformat)
+        logger.addHandler(consolelog)
+
+
+    while True:
+        try:
+            event = eventHandler.waitEvent(0.25)
+            if first:
+                first = False
+                logger.info("ToasterUI waiting for events")
+
+            if event is None:
+                if main.shutdown > 0:
+                    break
+                continue
+
+            helper.eventHandler(event)
+
+            # pylint: disable=protected-access
+            # the code will look into the protected variables of the event; no easy way around this
+
+            if isinstance(event, bb.event.BuildStarted):
+                buildinfohelper.store_started_build(event)
+
+            if isinstance(event, (bb.build.TaskStarted, bb.build.TaskSucceeded, bb.build.TaskFailedSilent)):
+                buildinfohelper.update_and_store_task(event)
+                logger.warn("Logfile for task %s", event.logfile)
+                continue
+
+            if isinstance(event, bb.build.TaskBase):
+                logger.info(event._message)
+
+            if isinstance(event, bb.event.LogExecTTY):
+                logger.warn(event.msg)
+                continue
+
+            if isinstance(event, logging.LogRecord):
+                if event.levelno == -1:
+                    event.levelno = formatter.ERROR
+
+                buildinfohelper.store_log_event(event)
+                if event.levelno >= formatter.ERROR:
+                    errors = errors + 1
+                elif event.levelno == formatter.WARNING:
+                    warnings = warnings + 1
+                # For "normal" logging conditions, don't show note logs from tasks
+                # but do show them if the user has changed the default log level to
+                # include verbose/debug messages
+                if event.taskpid != 0 and event.levelno <= formatter.NOTE:
+                    continue
+
+                logger.handle(event)
+                continue
+
+            if isinstance(event, bb.build.TaskFailed):
+                buildinfohelper.update_and_store_task(event)
+                logfile = event.logfile
+                if logfile and os.path.exists(logfile):
+                    bb.error("Logfile of failure stored in: %s" % logfile)
+                continue
+
+            # these events are unprocessed now, but may be used in the future to log
+            # timing and error informations from the parsing phase in Toaster
+            if isinstance(event, (bb.event.SanityCheckPassed, bb.event.SanityCheck)):
+                continue
+            if isinstance(event, bb.event.ParseStarted):
+                continue
+            if isinstance(event, bb.event.ParseProgress):
+                continue
+            if isinstance(event, bb.event.ParseCompleted):
+                continue
+            if isinstance(event, bb.event.CacheLoadStarted):
+                continue
+            if isinstance(event, bb.event.CacheLoadProgress):
+                continue
+            if isinstance(event, bb.event.CacheLoadCompleted):
+                continue
+            if isinstance(event, bb.event.MultipleProviders):
+                logger.info("multiple providers are available for %s%s (%s)", event._is_runtime and "runtime " or "",
+                            event._item,
+                            ", ".join(event._candidates))
+                logger.info("consider defining a PREFERRED_PROVIDER entry to match %s", event._item)
+                continue
+
+            if isinstance(event, bb.event.NoProvider):
+                errors = errors + 1
+                if event._runtime:
+                    r = "R"
+                else:
+                    r = ""
+
+                if event._dependees:
+                    text = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)" % (r, event._item, ", ".join(event._dependees), r)
+                else:
+                    text = "Nothing %sPROVIDES '%s'" % (r, event._item)
+
+                logger.error(text)
+                if event._reasons:
+                    for reason in event._reasons:
+                        logger.error("%s", reason)
+                        text += reason
+                buildinfohelper.store_log_error(text)
+                continue
+
+            if isinstance(event, bb.event.ConfigParsed):
+                continue
+            if isinstance(event, bb.event.RecipeParsed):
+                continue
+
+            # end of saved events
+
+            if isinstance(event, (bb.runqueue.sceneQueueTaskStarted, bb.runqueue.runQueueTaskStarted, bb.runqueue.runQueueTaskSkipped)):
+                buildinfohelper.store_started_task(event)
+                continue
+
+            if isinstance(event, bb.runqueue.runQueueTaskCompleted):
+                buildinfohelper.update_and_store_task(event)
+                continue
+
+            if isinstance(event, bb.runqueue.runQueueTaskFailed):
+                buildinfohelper.update_and_store_task(event)
+                taskfailures.append(event.taskstring)
+                logger.error("Task %s (%s) failed with exit code '%s'",
+                             event.taskid, event.taskstring, event.exitcode)
+                continue
+
+            if isinstance(event, (bb.runqueue.sceneQueueTaskCompleted, bb.runqueue.sceneQueueTaskFailed)):
+                buildinfohelper.update_and_store_task(event)
+                continue
+
+
+            if isinstance(event, (bb.event.TreeDataPreparationStarted, bb.event.TreeDataPreparationCompleted)):
+                continue
+
+            if isinstance(event, (bb.event.BuildCompleted, bb.command.CommandFailed)):
+
+                errorcode = 0
+                if isinstance(event, bb.command.CommandFailed):
+                    errors += 1
+                    errorcode = 1
+                    logger.error("Command execution failed: %s", event.error)
+
+                # update the build info helper on BuildCompleted, not on CommandXXX
+                buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
+                buildinfohelper.close(errorcode)
+                # mark the log output; controllers may kill the toasterUI after seeing this log
+                logger.info("ToasterUI build done 1, brbe: %s", buildinfohelper.brbe )
+
+                # we start a new build info
+                if buildinfohelper.brbe is not None:
+
+                    logger.debug("ToasterUI under BuildEnvironment management - exiting after the build")
+                    server.terminateServer()
+                else:
+                    logger.debug("ToasterUI prepared for new build")
+                    errors = 0
+                    warnings = 0
+                    taskfailures = []
+                    buildinfohelper = BuildInfoHelper(server, build_history_enabled)
+
+                logger.info("ToasterUI build done 2")
+                continue
+
+            if isinstance(event, (bb.command.CommandCompleted,
+                                  bb.command.CommandFailed,
+                                  bb.command.CommandExit)):
+                errorcode = 0
+
+                continue
+
+            if isinstance(event, bb.event.MetadataEvent):
+                if event.type == "SinglePackageInfo":
+                    buildinfohelper.store_build_package_information(event)
+                elif event.type == "LayerInfo":
+                    buildinfohelper.store_layer_info(event)
+                elif event.type == "BuildStatsList":
+                    buildinfohelper.store_tasks_stats(event)
+                elif event.type == "ImagePkgList":
+                    buildinfohelper.store_target_package_data(event)
+                elif event.type == "MissedSstate":
+                    buildinfohelper.store_missed_state_tasks(event)
+                elif event.type == "ImageFileSize":
+                    buildinfohelper.update_target_image_file(event)
+                elif event.type == "ArtifactFileSize":
+                    buildinfohelper.update_artifact_image_file(event)
+                elif event.type == "LicenseManifestPath":
+                    buildinfohelper.store_license_manifest_path(event)
+                else:
+                    logger.error("Unprocessed MetadataEvent %s ", str(event))
+                continue
+
+            if isinstance(event, bb.cooker.CookerExit):
+                # exit when the server exits
+                break
+
+            # ignore
+            if isinstance(event, (bb.event.BuildBase,
+                                  bb.event.StampUpdate,
+                                  bb.event.RecipePreFinalise,
+                                  bb.runqueue.runQueueEvent,
+                                  bb.runqueue.runQueueExitWait,
+                                  bb.event.OperationProgress,
+                                  bb.command.CommandFailed,
+                                  bb.command.CommandExit,
+                                  bb.command.CommandCompleted)):
+                continue
+
+            if isinstance(event, bb.event.DepTreeGenerated):
+                buildinfohelper.store_dependency_information(event)
+                continue
+
+            logger.error("Unknown event: %s", event)
+            return_value += 1
+
+        except EnvironmentError as ioerror:
+            # ignore interrupted io
+            if ioerror.args[0] == 4:
+                pass
+        except KeyboardInterrupt:
+            main.shutdown = 1
+        except Exception as e:
+            # print errors to log
+            import traceback
+            from pprint import pformat
+            exception_data = traceback.format_exc()
+            logger.error("%s\n%s" , e, exception_data)
+
+            _, _, tb = sys.exc_info()
+            if tb is not None:
+                curr = tb
+                while curr is not None:
+                    logger.warn("Error data dump %s\n%s\n" , traceback.format_tb(curr,1), pformat(curr.tb_frame.f_locals))
+                    curr = curr.tb_next
+
+            # save them to database, if possible; if it fails, we already logged to console.
+            try:
+                buildinfohelper.store_log_exception("%s\n%s" % (str(e), exception_data))
+            except Exception as ce:
+                logger.error("CRITICAL - Failed to to save toaster exception to the database: %s", str(ce))
+
+            # make sure we return with an error
+            return_value += 1
+
+    if interrupted:
+        if return_value == 0:
+            return_value += 1
+
+    logger.warn("Return value is %d", return_value)
+    return return_value
diff --git a/bitbake/lib/bb/ui/uievent.py b/bitbake/lib/bb/ui/uievent.py
new file mode 100644
index 0000000..7fc50c7
--- /dev/null
+++ b/bitbake/lib/bb/ui/uievent.py
@@ -0,0 +1,155 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Copyright (C) 2006 - 2007  Michael 'Mickey' Lauer
+# Copyright (C) 2006 - 2007  Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+"""
+Use this class to fork off a thread to recieve event callbacks from the bitbake
+server and queue them for the UI to process. This process must be used to avoid
+client/server deadlocks.
+"""
+
+import socket, threading, pickle
+from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
+
+class BBUIEventQueue:
+    def __init__(self, BBServer, clientinfo=("localhost, 0")):
+
+        self.eventQueue = []
+        self.eventQueueLock = threading.Lock()
+        self.eventQueueNotify = threading.Event()
+
+        self.BBServer = BBServer
+        self.clientinfo = clientinfo
+
+        server = UIXMLRPCServer(self.clientinfo)
+        self.host, self.port = server.socket.getsockname()
+
+        server.register_function( self.system_quit, "event.quit" )
+        server.register_function( self.send_event, "event.sendpickle" )
+        server.socket.settimeout(1)
+
+        self.EventHandler = None
+        count_tries = 0
+
+        # the event handler registration may fail here due to cooker being in invalid state
+        # this is a transient situation, and we should retry a couple of times before
+        # giving up
+
+        while self.EventHandler == None and count_tries < 5:
+            self.EventHandle = self.BBServer.registerEventHandler(self.host, self.port)
+
+            if (self.EventHandle != None):
+                break
+
+            bb.warn("Could not register UI event handler %s:%d, retry" % (self.host, self.port))
+            count_tries += 1
+            import time
+            time.sleep(1)
+
+
+        if self.EventHandle == None:
+            raise Exception("Could not register UI event handler")
+
+        self.server = server
+
+        self.t = threading.Thread()
+        self.t.setDaemon(True)
+        self.t.run = self.startCallbackHandler
+        self.t.start()
+
+    def getEvent(self):
+
+        self.eventQueueLock.acquire()
+
+        if len(self.eventQueue) == 0:
+            self.eventQueueLock.release()
+            return None
+
+        item = self.eventQueue.pop(0)
+
+        if len(self.eventQueue) == 0:
+            self.eventQueueNotify.clear()
+
+        self.eventQueueLock.release()
+        return item
+
+    def waitEvent(self, delay):
+        self.eventQueueNotify.wait(delay)
+        return self.getEvent()
+
+    def queue_event(self, event):
+        self.eventQueueLock.acquire()
+        self.eventQueue.append(event)
+        self.eventQueueNotify.set()
+        self.eventQueueLock.release()
+
+    def send_event(self, event):
+        self.queue_event(pickle.loads(event))
+
+    def startCallbackHandler(self):
+
+        self.server.timeout = 1
+        while not self.server.quit:
+            try:
+                self.server.handle_request()
+            except Exception as e:
+                import traceback
+                logger.error("BBUIEventQueue.startCallbackHandler: Exception while trying to handle request: %s\n%s" % (e, traceback.format_exc(e)))
+
+        self.server.server_close()
+
+    def system_quit( self ):
+        """
+        Shut down the callback thread
+        """
+        try:
+            self.BBServer.unregisterEventHandler(self.EventHandle)
+        except:
+            pass
+        self.server.quit = True
+
+class UIXMLRPCServer (SimpleXMLRPCServer):
+
+    def __init__( self, interface ):
+        self.quit = False
+        SimpleXMLRPCServer.__init__( self,
+                                    interface,
+                                    requestHandler=SimpleXMLRPCRequestHandler,
+                                    logRequests=False, allow_none=True)
+
+    def get_request(self):
+        while not self.quit:
+            try:
+                sock, addr = self.socket.accept()
+                sock.settimeout(1)
+                return (sock, addr)
+            except socket.timeout:
+                pass
+        return (None, None)
+
+    def close_request(self, request):
+        if request is None:
+            return
+        SimpleXMLRPCServer.close_request(self, request)
+
+    def process_request(self, request, client_address):
+        if request is None:
+            return
+        SimpleXMLRPCServer.process_request(self, request, client_address)
+
diff --git a/bitbake/lib/bb/ui/uihelper.py b/bitbake/lib/bb/ui/uihelper.py
new file mode 100644
index 0000000..a703387
--- /dev/null
+++ b/bitbake/lib/bb/ui/uihelper.py
@@ -0,0 +1,100 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Copyright (C) 2006 - 2007  Michael 'Mickey' Lauer
+# Copyright (C) 2006 - 2007  Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import bb.build
+
+class BBUIHelper:
+    def __init__(self):
+        self.needUpdate = False
+        self.running_tasks = {}
+        # Running PIDs preserves the order tasks were executed in
+        self.running_pids = []
+        self.failed_tasks = []
+        self.tasknumber_current = 0
+        self.tasknumber_total = 0
+
+    def eventHandler(self, event):
+        if isinstance(event, bb.build.TaskStarted):
+            self.running_tasks[event.pid] = { 'title' : "%s %s" % (event._package, event._task) }
+            self.running_pids.append(event.pid)
+            self.needUpdate = True
+        if isinstance(event, bb.build.TaskSucceeded):
+            del self.running_tasks[event.pid]
+            self.running_pids.remove(event.pid)
+            self.needUpdate = True
+        if isinstance(event, bb.build.TaskFailedSilent):
+            del self.running_tasks[event.pid]
+            self.running_pids.remove(event.pid)
+            # Don't add to the failed tasks list since this is e.g. a setscene task failure
+            self.needUpdate = True
+        if isinstance(event, bb.build.TaskFailed):
+            del self.running_tasks[event.pid]
+            self.running_pids.remove(event.pid)
+            self.failed_tasks.append( { 'title' : "%s %s" % (event._package, event._task)})
+            self.needUpdate = True
+        if isinstance(event, bb.runqueue.runQueueTaskStarted) or isinstance(event, bb.runqueue.sceneQueueTaskStarted):
+            self.tasknumber_current = event.stats.completed + event.stats.active + event.stats.failed + 1
+            self.tasknumber_total = event.stats.total
+            self.needUpdate = True
+
+    def getTasks(self):
+        self.needUpdate = False
+        return (self.running_tasks, self.failed_tasks)
+
+    def findServerDetails(self):
+        import sys
+        import optparse
+        from bb.server.xmlrpc import BitbakeServerInfo, BitBakeServerConnection
+        host = ""
+        port = 0
+        bind = ""
+        parser = optparse.OptionParser(
+            usage = """%prog -H host -P port -B bindaddr""")
+
+        parser.add_option("-H", "--host", help = "Bitbake server's IP address",
+                   action = "store", dest = "host", default = None)
+
+        parser.add_option("-P", "--port", help = "Bitbake server's Port number",
+                   action = "store", dest = "port", default = None)
+
+        parser.add_option("-B", "--bind", help = "Hob2 local bind address",
+                   action = "store", dest = "bind", default = None)
+
+        options, args = parser.parse_args(sys.argv)
+        for key, val in options.__dict__.items():
+            if key == 'host' and val:
+                host = val
+            elif key == 'port' and val:
+                port = int(val)
+            elif key == 'bind' and val:
+                bind = val
+
+        if not host or not port or not bind:
+            parser.print_usage()
+            sys.exit(1)
+
+        serverinfo = BitbakeServerInfo(host, port)
+        clientinfo = (bind, 0)
+        connection = BitBakeServerConnection(serverinfo, clientinfo)
+
+        server = connection.connection
+        eventHandler = connection.events
+
+        return server, eventHandler, host, bind
+
diff --git a/bitbake/lib/bb/utils.py b/bitbake/lib/bb/utils.py
new file mode 100644
index 0000000..91faa49
--- /dev/null
+++ b/bitbake/lib/bb/utils.py
@@ -0,0 +1,1312 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+"""
+BitBake Utility Functions
+"""
+
+# Copyright (C) 2004 Michael Lauer
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import re, fcntl, os, string, stat, shutil, time
+import sys
+import errno
+import logging
+import bb
+import bb.msg
+import multiprocessing
+import fcntl
+import subprocess
+import glob
+import fnmatch
+import traceback
+import errno
+import signal
+from commands import getstatusoutput
+from contextlib import contextmanager
+from ctypes import cdll
+
+
+logger = logging.getLogger("BitBake.Util")
+
+def clean_context():
+    return {
+        "os": os,
+        "bb": bb,
+        "time": time,
+    }
+
+def get_context():
+    return _context
+    
+
+def set_context(ctx):
+    _context = ctx
+
+# Context used in better_exec, eval
+_context = clean_context()
+
+class VersionStringException(Exception):
+    """Exception raised when an invalid version specification is found"""
+
+def explode_version(s):
+    r = []
+    alpha_regexp = re.compile('^([a-zA-Z]+)(.*)$')
+    numeric_regexp = re.compile('^(\d+)(.*)$')
+    while (s != ''):
+        if s[0] in string.digits:
+            m = numeric_regexp.match(s)
+            r.append((0, int(m.group(1))))
+            s = m.group(2)
+            continue
+        if s[0] in string.letters:
+            m = alpha_regexp.match(s)
+            r.append((1, m.group(1)))
+            s = m.group(2)
+            continue
+        if s[0] == '~':
+            r.append((-1, s[0]))
+        else:
+            r.append((2, s[0]))
+        s = s[1:]
+    return r
+
+def split_version(s):
+    """Split a version string into its constituent parts (PE, PV, PR)"""
+    s = s.strip(" <>=")
+    e = 0
+    if s.count(':'):
+        e = int(s.split(":")[0])
+        s = s.split(":")[1]
+    r = ""
+    if s.count('-'):
+        r = s.rsplit("-", 1)[1]
+        s = s.rsplit("-", 1)[0]
+    v = s
+    return (e, v, r)
+
+def vercmp_part(a, b):
+    va = explode_version(a)
+    vb = explode_version(b)
+    while True:
+        if va == []:
+            (oa, ca) = (0, None)
+        else:
+            (oa, ca) = va.pop(0)
+        if vb == []:
+            (ob, cb) = (0, None)
+        else:
+            (ob, cb) = vb.pop(0)
+        if (oa, ca) == (0, None) and (ob, cb) == (0, None):
+            return 0
+        if oa < ob:
+            return -1
+        elif oa > ob:
+            return 1
+        elif ca < cb:
+            return -1
+        elif ca > cb:
+            return 1
+
+def vercmp(ta, tb):
+    (ea, va, ra) = ta
+    (eb, vb, rb) = tb
+
+    r = int(ea or 0) - int(eb or 0)
+    if (r == 0):
+        r = vercmp_part(va, vb)
+    if (r == 0):
+        r = vercmp_part(ra, rb)
+    return r
+
+def vercmp_string(a, b):
+    ta = split_version(a)
+    tb = split_version(b)
+    return vercmp(ta, tb)
+
+def vercmp_string_op(a, b, op):
+    """
+    Compare two versions and check if the specified comparison operator matches the result of the comparison.
+    This function is fairly liberal about what operators it will accept since there are a variety of styles
+    depending on the context.
+    """
+    res = vercmp_string(a, b)
+    if op in ('=', '=='):
+        return res == 0
+    elif op == '<=':
+        return res <= 0
+    elif op == '>=':
+        return res >= 0
+    elif op in ('>', '>>'):
+        return res > 0
+    elif op in ('<', '<<'):
+        return res < 0
+    elif op == '!=':
+        return res != 0
+    else:
+        raise VersionStringException('Unsupported comparison operator "%s"' % op)
+
+def explode_deps(s):
+    """
+    Take an RDEPENDS style string of format:
+    "DEPEND1 (optional version) DEPEND2 (optional version) ..."
+    and return a list of dependencies.
+    Version information is ignored.
+    """
+    r = []
+    l = s.split()
+    flag = False
+    for i in l:
+        if i[0] == '(':
+            flag = True
+            #j = []
+        if not flag:
+            r.append(i)
+        #else:
+        #    j.append(i)
+        if flag and i.endswith(')'):
+            flag = False
+            # Ignore version
+            #r[-1] += ' ' + ' '.join(j)
+    return r
+
+def explode_dep_versions2(s):
+    """
+    Take an RDEPENDS style string of format:
+    "DEPEND1 (optional version) DEPEND2 (optional version) ..."
+    and return a dictionary of dependencies and versions.
+    """
+    r = {}
+    l = s.replace(",", "").split()
+    lastdep = None
+    lastcmp = ""
+    lastver = ""
+    incmp = False
+    inversion = False
+    for i in l:
+        if i[0] == '(':
+            incmp = True
+            i = i[1:].strip()
+            if not i:
+                continue
+
+        if incmp:
+            incmp = False
+            inversion = True
+            # This list is based on behavior and supported comparisons from deb, opkg and rpm.
+            #
+            # Even though =<, <<, ==, !=, =>, and >> may not be supported, 
+            # we list each possibly valid item. 
+            # The build system is responsible for validation of what it supports.
+            if i.startswith(('<=', '=<', '<<', '==', '!=', '>=', '=>', '>>')):
+                lastcmp = i[0:2]
+                i = i[2:]
+            elif i.startswith(('<', '>', '=')):
+                lastcmp = i[0:1]
+                i = i[1:]
+            else:
+                # This is an unsupported case!
+                raise VersionStringException('Invalid version specification in "(%s" - invalid or missing operator' % i)
+                lastcmp = (i or "")
+                i = ""
+            i.strip()
+            if not i:
+                continue
+
+        if inversion:
+            if i.endswith(')'):
+                i = i[:-1] or ""
+                inversion = False
+                if lastver and i:
+                    lastver += " "
+            if i:
+                lastver += i
+                if lastdep not in r:
+                    r[lastdep] = []
+                r[lastdep].append(lastcmp + " " + lastver)
+            continue
+
+        #if not inversion:
+        lastdep = i
+        lastver = ""
+        lastcmp = ""
+        if not (i in r and r[i]):
+            r[lastdep] = []
+
+    return r
+
+def explode_dep_versions(s):
+    r = explode_dep_versions2(s)
+    for d in r:
+        if not r[d]:
+            r[d] = None
+            continue
+        if len(r[d]) > 1:
+            bb.warn("explode_dep_versions(): Item %s appeared in dependency string '%s' multiple times with different values.  explode_dep_versions cannot cope with this." % (d, s))
+        r[d] = r[d][0]
+    return r
+
+def join_deps(deps, commasep=True):
+    """
+    Take the result from explode_dep_versions and generate a dependency string
+    """
+    result = []
+    for dep in deps:
+        if deps[dep]:
+            if isinstance(deps[dep], list):
+                for v in deps[dep]:
+                    result.append(dep + " (" + v + ")")
+            else:
+                result.append(dep + " (" + deps[dep] + ")")
+        else:
+            result.append(dep)
+    if commasep:
+        return ", ".join(result)
+    else:
+        return " ".join(result)
+
+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 range(min_line, max_line + 1):
+        if line == i:
+            error.append(' *** %.4d:%s' % (i, body[i-1].rstrip()))
+        else:
+            error.append('     %.4d:%s' % (i, body[i-1].rstrip()))
+    return error
+
+def better_compile(text, file, realfile, mode = "exec"):
+    """
+    A better compile method. This method
+    will print the offending lines.
+    """
+    try:
+        return compile(text, file, mode)
+    except Exception as e:
+        error = []
+        # split the text into lines again
+        body = text.split('\n')
+        error.append("Error in compiling python function in %s:\n" % realfile)
+        if e.lineno:
+            error.append("The code lines resulting in this error were:")
+            error.extend(_print_trace(body, e.lineno))
+        else:
+            error.append("The function causing this error was:")
+            for line in body:
+                error.append(line)
+        error.append("%s: %s" % (e.__class__.__name__, str(e)))
+
+        logger.error("\n".join(error))
+
+        e = bb.BBHandledException(e)
+        raise e
+
+def _print_exception(t, value, tb, realfile, text, context):
+    error = []
+    try:
+        exception = traceback.format_exception_only(t, value)
+        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 = tb.tb_lineno
+
+        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]:
+                # 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:
+                    error.extend(_print_trace(text.split('\n'), tbextract[level+1][1]))
+                else:
+                    error.append(tbformat[level+1])
+            else:
+                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, get_context(), context)
+    except (bb.BBHandledException, bb.parse.SkipRecipe, bb.build.FuncFailed, bb.data_smart.ExpansionError):
+        # Error already shown so passthrough, no need for traceback
+        raise
+    except Exception as e:
+        (t, value, tb) = sys.exc_info()
+        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
+
+def simple_exec(code, context):
+    exec(code, get_context(), context)
+
+def better_eval(source, locals):
+    return eval(source, get_context(), locals)
+
+@contextmanager
+def fileslocked(files):
+    """Context manager for locking and unlocking file locks."""
+    locks = []
+    if files:
+        for lockfile in files:
+            locks.append(bb.utils.lockfile(lockfile))
+
+    yield
+
+    for lock in locks:
+        bb.utils.unlockfile(lock)
+
+@contextmanager
+def timeout(seconds):
+    def timeout_handler(signum, frame):
+        pass
+
+    original_handler = signal.signal(signal.SIGALRM, timeout_handler)
+
+    try:
+        signal.alarm(seconds)
+        yield
+    finally:
+        signal.alarm(0)
+        signal.signal(signal.SIGALRM, original_handler)
+
+def lockfile(name, shared=False, retry=True, block=False):
+    """
+    Use the specified file as a lock file, return when the lock has
+    been acquired. Returns a variable to pass to unlockfile().
+    Parameters:
+        retry: True to re-try locking if it fails, False otherwise
+        block: True to block until the lock succeeds, False otherwise
+    The retry and block parameters are kind of equivalent unless you
+    consider the possibility of sending a signal to the process to break
+    out - at which point you want block=True rather than retry=True.
+    """
+    dirname = os.path.dirname(name)
+    mkdirhier(dirname)
+
+    if not os.access(dirname, os.W_OK):
+        logger.error("Unable to acquire lock '%s', directory is not writable",
+                     name)
+        sys.exit(1)
+
+    op = fcntl.LOCK_EX
+    if shared:
+        op = fcntl.LOCK_SH
+    if not retry and not block:
+        op = op | fcntl.LOCK_NB
+
+    while True:
+        # If we leave the lockfiles lying around there is no problem
+        # but we should clean up after ourselves. This gives potential
+        # for races though. To work around this, when we acquire the lock
+        # we check the file we locked was still the lock file on disk.
+        # by comparing inode numbers. If they don't match or the lockfile
+        # no longer exists, we start again.
+
+        # This implementation is unfair since the last person to request the
+        # lock is the most likely to win it.
+
+        try:
+            lf = open(name, 'a+')
+            fileno = lf.fileno()
+            fcntl.flock(fileno, op)
+            statinfo = os.fstat(fileno)
+            if os.path.exists(lf.name):
+                statinfo2 = os.stat(lf.name)
+                if statinfo.st_ino == statinfo2.st_ino:
+                    return lf
+            lf.close()
+        except Exception:
+            try:
+                lf.close()
+            except Exception:
+                pass
+            pass
+        if not retry:
+            return None
+
+def unlockfile(lf):
+    """
+    Unlock a file locked using lockfile()
+    """
+    try:
+        # If we had a shared lock, we need to promote to exclusive before
+        # removing the lockfile. Attempt this, ignore failures.
+        fcntl.flock(lf.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
+        os.unlink(lf.name)
+    except (IOError, OSError):
+        pass
+    fcntl.flock(lf.fileno(), fcntl.LOCK_UN)
+    lf.close()
+
+def md5_file(filename):
+    """
+    Return the hex string representation of the MD5 checksum of filename.
+    """
+    try:
+        import hashlib
+        m = hashlib.md5()
+    except ImportError:
+        import md5
+        m = md5.new()
+
+    with open(filename, "rb") as f:
+        for line in f:
+            m.update(line)
+    return m.hexdigest()
+
+def sha256_file(filename):
+    """
+    Return the hex string representation of the 256-bit SHA checksum of
+    filename.  On Python 2.4 this will return None, so callers will need to
+    handle that by either skipping SHA checks, or running a standalone sha256sum
+    binary.
+    """
+    try:
+        import hashlib
+    except ImportError:
+        return None
+
+    s = hashlib.sha256()
+    with open(filename, "rb") as f:
+        for line in f:
+            s.update(line)
+    return s.hexdigest()
+
+def preserved_envvars_exported():
+    """Variables which are taken from the environment and placed in and exported
+    from the metadata"""
+    return [
+        'BB_TASKHASH',
+        'HOME',
+        'LOGNAME',
+        'PATH',
+        'PWD',
+        'SHELL',
+        'TERM',
+        'USER',
+    ]
+
+def preserved_envvars():
+    """Variables which are taken from the environment and placed in the metadata"""
+    v = [
+        'BBPATH',
+        'BB_PRESERVE_ENV',
+        'BB_ENV_WHITELIST',
+        'BB_ENV_EXTRAWHITE',
+    ]
+    return v + preserved_envvars_exported()
+
+def filter_environment(good_vars):
+    """
+    Create a pristine environment for bitbake. This will remove variables that
+    are not known and may influence the build in a negative way.
+    """
+
+    removed_vars = {}
+    for key in os.environ.keys():
+        if key in good_vars:
+            continue
+
+        removed_vars[key] = os.environ[key]
+        os.unsetenv(key)
+        del os.environ[key]
+
+    if removed_vars:
+        logger.debug(1, "Removed the following variables from the environment: %s", ", ".join(removed_vars.keys()))
+
+    return removed_vars
+
+def approved_variables():
+    """
+    Determine and return the list of whitelisted variables which are approved
+    to remain in the environment.
+    """
+    if 'BB_PRESERVE_ENV' in os.environ:
+        return os.environ.keys()
+    approved = []
+    if 'BB_ENV_WHITELIST' in os.environ:
+        approved = os.environ['BB_ENV_WHITELIST'].split()
+        approved.extend(['BB_ENV_WHITELIST'])
+    else:
+        approved = preserved_envvars()
+    if 'BB_ENV_EXTRAWHITE' in os.environ:
+        approved.extend(os.environ['BB_ENV_EXTRAWHITE'].split())
+        if 'BB_ENV_EXTRAWHITE' not in approved:
+            approved.extend(['BB_ENV_EXTRAWHITE'])
+    return approved
+
+def clean_environment():
+    """
+    Clean up any spurious environment variables. This will remove any
+    variables the user hasn't chosen to preserve.
+    """
+    if 'BB_PRESERVE_ENV' not in os.environ:
+        good_vars = approved_variables()
+        return filter_environment(good_vars)
+
+    return {}
+
+def empty_environment():
+    """
+    Remove all variables from the environment.
+    """
+    for s in os.environ.keys():
+        os.unsetenv(s)
+        del os.environ[s]
+
+def build_environment(d):
+    """
+    Build an environment from all exported variables.
+    """
+    import bb.data
+    for var in bb.data.keys(d):
+        export = d.getVarFlag(var, "export")
+        if export:
+            os.environ[var] = d.getVar(var, True) or ""
+
+def _check_unsafe_delete_path(path):
+    """
+    Basic safeguard against recursively deleting something we shouldn't. If it returns True,
+    the caller should raise an exception with an appropriate message.
+    NOTE: This is NOT meant to be a security mechanism - just a guard against silly mistakes
+    with potentially disastrous results.
+    """
+    extra = ''
+    # HOME might not be /home/something, so in case we can get it, check against it
+    homedir = os.environ.get('HOME', '')
+    if homedir:
+        extra = '|%s' % homedir
+    if re.match('(/|//|/home|/home/[^/]*%s)$' % extra, os.path.abspath(path)):
+        return True
+    return False
+
+def remove(path, recurse=False):
+    """Equivalent to rm -f or rm -rf"""
+    if not path:
+        return
+    if recurse:
+        for name in glob.glob(path):
+            if _check_unsafe_delete_path(path):
+                raise Exception('bb.utils.remove: called with dangerous path "%s" and recurse=True, refusing to delete!' % path)
+        # shutil.rmtree(name) would be ideal but its too slow
+        subprocess.call(['rm', '-rf'] + glob.glob(path))
+        return
+    for name in glob.glob(path):
+        try:
+            os.unlink(name)
+        except OSError as exc:
+            if exc.errno != errno.ENOENT:
+                raise
+
+def prunedir(topdir):
+    # Delete everything reachable from the directory named in 'topdir'.
+    # CAUTION:  This is dangerous!
+    if _check_unsafe_delete_path(topdir):
+        raise Exception('bb.utils.prunedir: called with dangerous path "%s", refusing to delete!' % topdir)
+    for root, dirs, files in os.walk(topdir, topdown = False):
+        for name in files:
+            os.remove(os.path.join(root, name))
+        for name in dirs:
+            if os.path.islink(os.path.join(root, name)):
+                os.remove(os.path.join(root, name))
+            else:
+                os.rmdir(os.path.join(root, name))
+    os.rmdir(topdir)
+
+#
+# Could also use return re.compile("(%s)" % "|".join(map(re.escape, suffixes))).sub(lambda mo: "", var)
+# but thats possibly insane and suffixes is probably going to be small
+#
+def prune_suffix(var, suffixes, d):
+    # See if var ends with any of the suffixes listed and
+    # remove it if found
+    for suffix in suffixes:
+        if var.endswith(suffix):
+            return var.replace(suffix, "")
+    return var
+
+def mkdirhier(directory):
+    """Create a directory like 'mkdir -p', but does not complain if
+    directory already exists like os.makedirs
+    """
+
+    try:
+        os.makedirs(directory)
+    except OSError as e:
+        if e.errno != errno.EEXIST:
+            raise e
+
+def movefile(src, dest, newmtime = None, sstat = None):
+    """Moves a file from src to dest, preserving all permissions and
+    attributes; mtime will be preserved even when moving across
+    filesystems.  Returns true on success and false on failure. Move is
+    atomic.
+    """
+
+    #print "movefile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
+    try:
+        if not sstat:
+            sstat = os.lstat(src)
+    except Exception as e:
+        print("movefile: Stating source file failed...", e)
+        return None
+
+    destexists = 1
+    try:
+        dstat = os.lstat(dest)
+    except:
+        dstat = os.lstat(os.path.dirname(dest))
+        destexists = 0
+
+    if destexists:
+        if stat.S_ISLNK(dstat[stat.ST_MODE]):
+            try:
+                os.unlink(dest)
+                destexists = 0
+            except Exception as e:
+                pass
+
+    if stat.S_ISLNK(sstat[stat.ST_MODE]):
+        try:
+            target = os.readlink(src)
+            if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
+                os.unlink(dest)
+            os.symlink(target, dest)
+            #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
+            os.unlink(src)
+            return os.lstat(dest)
+        except Exception as e:
+            print("movefile: failed to properly create symlink:", dest, "->", target, e)
+            return None
+
+    renamefailed = 1
+    if sstat[stat.ST_DEV] == dstat[stat.ST_DEV]:
+        try:
+            # os.rename needs to know the dest path ending with file name
+            # so append the file name to a path only if it's a dir specified
+            srcfname = os.path.basename(src)
+            destpath = os.path.join(dest, srcfname) if os.path.isdir(dest) \
+                        else dest
+            os.rename(src, destpath)
+            renamefailed = 0
+        except Exception as e:
+            if e[0] != errno.EXDEV:
+                # Some random error.
+                print("movefile: Failed to move", src, "to", dest, e)
+                return None
+            # Invalid cross-device-link 'bind' mounted or actually Cross-Device
+
+    if renamefailed:
+        didcopy = 0
+        if stat.S_ISREG(sstat[stat.ST_MODE]):
+            try: # For safety copy then move it over.
+                shutil.copyfile(src, dest + "#new")
+                os.rename(dest + "#new", dest)
+                didcopy = 1
+            except Exception as e:
+                print('movefile: copy', src, '->', dest, 'failed.', e)
+                return None
+        else:
+            #we don't yet handle special, so we need to fall back to /bin/mv
+            a = getstatusoutput("/bin/mv -f " + "'" + src + "' '" + dest + "'")
+            if a[0] != 0:
+                print("movefile: Failed to move special file:" + src + "' to '" + dest + "'", a)
+                return None # failure
+        try:
+            if didcopy:
+                os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
+                os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
+                os.unlink(src)
+        except Exception as e:
+            print("movefile: Failed to chown/chmod/unlink", dest, e)
+            return None
+
+    if newmtime:
+        os.utime(dest, (newmtime, newmtime))
+    else:
+        os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
+        newmtime = sstat[stat.ST_MTIME]
+    return newmtime
+
+def copyfile(src, dest, newmtime = None, sstat = None):
+    """
+    Copies a file from src to dest, preserving all permissions and
+    attributes; mtime will be preserved even when moving across
+    filesystems.  Returns true on success and false on failure.
+    """
+    #print "copyfile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
+    try:
+        if not sstat:
+            sstat = os.lstat(src)
+    except Exception as e:
+        logger.warn("copyfile: stat of %s failed (%s)" % (src, e))
+        return False
+
+    destexists = 1
+    try:
+        dstat = os.lstat(dest)
+    except:
+        dstat = os.lstat(os.path.dirname(dest))
+        destexists = 0
+
+    if destexists:
+        if stat.S_ISLNK(dstat[stat.ST_MODE]):
+            try:
+                os.unlink(dest)
+                destexists = 0
+            except Exception as e:
+                pass
+
+    if stat.S_ISLNK(sstat[stat.ST_MODE]):
+        try:
+            target = os.readlink(src)
+            if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
+                os.unlink(dest)
+            os.symlink(target, dest)
+            #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
+            return os.lstat(dest)
+        except Exception as e:
+            logger.warn("copyfile: failed to create symlink %s to %s (%s)" % (dest, target, e))
+            return False
+
+    if stat.S_ISREG(sstat[stat.ST_MODE]):
+        try:
+            srcchown = False
+            if not os.access(src, os.R_OK):
+                # Make sure we can read it
+                srcchown = True
+                os.chmod(src, sstat[stat.ST_MODE] | stat.S_IRUSR)
+
+            # For safety copy then move it over.
+            shutil.copyfile(src, dest + "#new")
+            os.rename(dest + "#new", dest)
+        except Exception as e:
+            logger.warn("copyfile: copy %s to %s failed (%s)" % (src, dest, e))
+            return False
+        finally:
+            if srcchown:
+                os.chmod(src, sstat[stat.ST_MODE])
+                os.utime(src, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
+
+    else:
+        #we don't yet handle special, so we need to fall back to /bin/mv
+        a = getstatusoutput("/bin/cp -f " + "'" + src + "' '" + dest + "'")
+        if a[0] != 0:
+            logger.warn("copyfile: failed to copy special file %s to %s (%s)" % (src, dest, a))
+            return False # failure
+    try:
+        os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
+        os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
+    except Exception as e:
+        logger.warn("copyfile: failed to chown/chmod %s (%s)" % (dest, e))
+        return False
+
+    if newmtime:
+        os.utime(dest, (newmtime, newmtime))
+    else:
+        os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
+        newmtime = sstat[stat.ST_MTIME]
+    return newmtime
+
+def which(path, item, direction = 0, history = False):
+    """
+    Locate a file in a PATH
+    """
+
+    hist = []
+    paths = (path or "").split(':')
+    if direction != 0:
+        paths.reverse()
+
+    for p in paths:
+        next = os.path.join(p, item)
+        hist.append(next)
+        if os.path.exists(next):
+            if not os.path.isabs(next):
+                next = os.path.abspath(next)
+            if history:
+                return next, hist
+            return next
+
+    if history:
+        return "", hist
+    return ""
+
+def to_boolean(string, default=None):
+    if not string:
+        return default
+
+    normalized = string.lower()
+    if normalized in ("y", "yes", "1", "true"):
+        return True
+    elif normalized in ("n", "no", "0", "false"):
+        return False
+    else:
+        raise ValueError("Invalid value for to_boolean: %s" % string)
+
+def contains(variable, checkvalues, truevalue, falsevalue, d):
+    val = d.getVar(variable, True)
+    if not val:
+        return falsevalue
+    val = set(val.split())
+    if isinstance(checkvalues, basestring):
+        checkvalues = set(checkvalues.split())
+    else:
+        checkvalues = set(checkvalues)
+    if checkvalues.issubset(val): 
+        return truevalue
+    return falsevalue
+
+def contains_any(variable, checkvalues, truevalue, falsevalue, d):
+    val = d.getVar(variable, True)
+    if not val:
+        return falsevalue
+    val = set(val.split())
+    if isinstance(checkvalues, basestring):
+        checkvalues = set(checkvalues.split())
+    else:
+        checkvalues = set(checkvalues)
+    if checkvalues & val:
+        return truevalue
+    return falsevalue
+
+def cpu_count():
+    return multiprocessing.cpu_count()
+
+def nonblockingfd(fd):
+    fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
+
+def process_profilelog(fn, pout = None):
+    # Either call with a list of filenames and set pout or a filename and optionally pout.
+    if not pout:
+        pout = fn + '.processed'
+    pout = open(pout, 'w')
+   
+    import pstats
+    if isinstance(fn, list):
+        p = pstats.Stats(*fn, stream=pout)
+    else:
+        p = pstats.Stats(fn, stream=pout)
+    p.sort_stats('time')
+    p.print_stats()
+    p.print_callers()
+    p.sort_stats('cumulative')
+    p.print_stats()
+
+    pout.flush()
+    pout.close()  
+
+#
+# Was present to work around multiprocessing pool bugs in python < 2.7.3
+#
+def multiprocessingpool(*args, **kwargs):
+
+    import multiprocessing.pool
+    #import multiprocessing.util
+    #multiprocessing.util.log_to_stderr(10)
+    # Deal with a multiprocessing bug where signals to the processes would be delayed until the work
+    # completes. Putting in a timeout means the signals (like SIGINT/SIGTERM) get processed.
+    def wrapper(func):
+        def wrap(self, timeout=None):
+            return func(self, timeout=timeout if timeout is not None else 1e100)
+        return wrap
+    multiprocessing.pool.IMapIterator.next = wrapper(multiprocessing.pool.IMapIterator.next)
+
+    return multiprocessing.Pool(*args, **kwargs)
+
+def exec_flat_python_func(func, *args, **kwargs):
+    """Execute a flat python function (defined with def funcname(args):...)"""
+    # Prepare a small piece of python code which calls the requested function
+    # To do this we need to prepare two things - a set of variables we can use to pass
+    # the values of arguments into the calling function, and the list of arguments for
+    # the function being called
+    context = {}
+    funcargs = []
+    # Handle unnamed arguments
+    aidx = 1
+    for arg in args:
+        argname = 'arg_%s' % aidx
+        context[argname] = arg
+        funcargs.append(argname)
+        aidx += 1
+    # Handle keyword arguments
+    context.update(kwargs)
+    funcargs.extend(['%s=%s' % (arg, arg) for arg in kwargs.iterkeys()])
+    code = 'retval = %s(%s)' % (func, ', '.join(funcargs))
+    comp = bb.utils.better_compile(code, '<string>', '<string>')
+    bb.utils.better_exec(comp, context, code, '<string>')
+    return context['retval']
+
+def edit_metadata(meta_lines, variables, varfunc, match_overrides=False):
+    """Edit lines from a recipe or config file and modify one or more
+    specified variable values set in the file using a specified callback
+    function. Lines are expected to have trailing newlines.
+    Parameters:
+        meta_lines: lines from the file; can be a list or an iterable
+            (e.g. file pointer)
+        variables: a list of variable names to look for. Functions
+            may also be specified, but must be specified with '()' at
+            the end of the name. Note that the function doesn't have
+            any intrinsic understanding of _append, _prepend, _remove,
+            or overrides, so these are considered as part of the name.
+            These values go into a regular expression, so regular
+            expression syntax is allowed.
+        varfunc: callback function called for every variable matching
+            one of the entries in the variables parameter. The function
+            should take four arguments:
+                varname: name of variable matched
+                origvalue: current value in file
+                op: the operator (e.g. '+=')
+                newlines: list of lines up to this point. You can use
+                    this to prepend lines before this variable setting
+                    if you wish.
+            and should return a three-element tuple:
+                newvalue: new value to substitute in, or None to drop
+                    the variable setting entirely. (If the removal
+                    results in two consecutive blank lines, one of the
+                    blank lines will also be dropped).
+                newop: the operator to use - if you specify None here,
+                    the original operation will be used.
+                indent: number of spaces to indent multi-line entries,
+                    or -1 to indent up to the level of the assignment
+                    and opening quote, or a string to use as the indent.
+                minbreak: True to allow the first element of a
+                    multi-line value to continue on the same line as
+                    the assignment, False to indent before the first
+                    element.
+        match_overrides: True to match items with _overrides on the end,
+            False otherwise
+    Returns a tuple:
+        updated:
+            True if changes were made, False otherwise.
+        newlines:
+            Lines after processing
+    """
+
+    var_res = {}
+    if match_overrides:
+        override_re = '(_[a-zA-Z0-9-_$(){}]+)?'
+    else:
+        override_re = ''
+    for var in variables:
+        if var.endswith('()'):
+            var_res[var] = re.compile('^(%s%s)[ \\t]*\([ \\t]*\)[ \\t]*{' % (var[:-2].rstrip(), override_re))
+        else:
+            var_res[var] = re.compile('^(%s%s)[ \\t]*[?+:.]*=[+.]*[ \\t]*(["\'])' % (var, override_re))
+
+    updated = False
+    varset_start = ''
+    varlines = []
+    newlines = []
+    in_var = None
+    full_value = ''
+    var_end = ''
+
+    def handle_var_end():
+        prerun_newlines = newlines[:]
+        op = varset_start[len(in_var):].strip()
+        (newvalue, newop, indent, minbreak) = varfunc(in_var, full_value, op, newlines)
+        changed = (prerun_newlines != newlines)
+
+        if newvalue is None:
+            # Drop the value
+            return True
+        elif newvalue != full_value or (newop not in [None, op]):
+            if newop not in [None, op]:
+                # Callback changed the operator
+                varset_new = "%s %s" % (in_var, newop)
+            else:
+                varset_new = varset_start
+
+            if isinstance(indent, (int, long)):
+                if indent == -1:
+                    indentspc = ' ' * (len(varset_new) + 2)
+                else:
+                    indentspc = ' ' * indent
+            else:
+                indentspc = indent
+            if in_var.endswith('()'):
+                # A function definition
+                if isinstance(newvalue, list):
+                    newlines.append('%s {\n%s%s\n}\n' % (varset_new, indentspc, ('\n%s' % indentspc).join(newvalue)))
+                else:
+                    if not newvalue.startswith('\n'):
+                        newvalue = '\n' + newvalue
+                    if not newvalue.endswith('\n'):
+                        newvalue = newvalue + '\n'
+                    newlines.append('%s {%s}\n' % (varset_new, newvalue))
+            else:
+                # Normal variable
+                if isinstance(newvalue, list):
+                    if not newvalue:
+                        # Empty list -> empty string
+                        newlines.append('%s ""\n' % varset_new)
+                    elif minbreak:
+                        # First item on first line
+                        if len(newvalue) == 1:
+                            newlines.append('%s "%s"\n' % (varset_new, newvalue[0]))
+                        else:
+                            newlines.append('%s "%s \\\n' % (varset_new, newvalue[0]))
+                            for item in newvalue[1:]:
+                                newlines.append('%s%s \\\n' % (indentspc, item))
+                            newlines.append('%s"\n' % indentspc)
+                    else:
+                        # No item on first line
+                        newlines.append('%s " \\\n' % varset_new)
+                        for item in newvalue:
+                            newlines.append('%s%s \\\n' % (indentspc, item))
+                        newlines.append('%s"\n' % indentspc)
+                else:
+                    newlines.append('%s "%s"\n' % (varset_new, newvalue))
+            return True
+        else:
+            # Put the old lines back where they were
+            newlines.extend(varlines)
+            # If newlines was touched by the function, we'll need to return True
+            return changed
+
+    checkspc = False
+
+    for line in meta_lines:
+        if in_var:
+            value = line.rstrip()
+            varlines.append(line)
+            if in_var.endswith('()'):
+                full_value += '\n' + value
+            else:
+                full_value += value[:-1]
+            if value.endswith(var_end):
+                if in_var.endswith('()'):
+                    if full_value.count('{') - full_value.count('}') >= 0:
+                        continue
+                full_value = full_value[:-1]
+                if handle_var_end():
+                    updated = True
+                    checkspc = True
+                in_var = None
+        else:
+            skip = False
+            for (varname, var_re) in var_res.iteritems():
+                res = var_re.match(line)
+                if res:
+                    isfunc = varname.endswith('()')
+                    if isfunc:
+                        splitvalue = line.split('{', 1)
+                        var_end = '}'
+                    else:
+                        var_end = res.groups()[-1]
+                        splitvalue = line.split(var_end, 1)
+                    varset_start = splitvalue[0].rstrip()
+                    value = splitvalue[1].rstrip()
+                    if not isfunc and value.endswith('\\'):
+                        value = value[:-1]
+                    full_value = value
+                    varlines = [line]
+                    in_var = res.group(1)
+                    if isfunc:
+                        in_var += '()'
+                    if value.endswith(var_end):
+                        full_value = full_value[:-1]
+                        if handle_var_end():
+                            updated = True
+                            checkspc = True
+                        in_var = None
+                    skip = True
+                    break
+            if not skip:
+                if checkspc:
+                    checkspc = False
+                    if newlines[-1] == '\n' and line == '\n':
+                        # Squash blank line if there are two consecutive blanks after a removal
+                        continue
+                newlines.append(line)
+    return (updated, newlines)
+
+
+def edit_metadata_file(meta_file, variables, varfunc):
+    """Edit a recipe or config file and modify one or more specified
+    variable values set in the file using a specified callback function.
+    The file is only written to if the value(s) actually change.
+    This is basically the file version of edit_metadata(), see that
+    function's description for parameter/usage information.
+    Returns True if the file was written to, False otherwise.
+    """
+    with open(meta_file, 'r') as f:
+        (updated, newlines) = edit_metadata(f, variables, varfunc)
+    if updated:
+        with open(meta_file, 'w') as f:
+            f.writelines(newlines)
+    return updated
+
+
+def edit_bblayers_conf(bblayers_conf, add, remove):
+    """Edit bblayers.conf, adding and/or removing layers"""
+
+    import fnmatch
+
+    def remove_trailing_sep(pth):
+        if pth and pth[-1] == os.sep:
+            pth = pth[:-1]
+        return pth
+
+    def layerlist_param(value):
+        if not value:
+            return []
+        elif isinstance(value, list):
+            return [remove_trailing_sep(x) for x in value]
+        else:
+            return [remove_trailing_sep(value)]
+
+    notadded = []
+    notremoved = []
+
+    addlayers = layerlist_param(add)
+    removelayers = layerlist_param(remove)
+
+    # Need to use a list here because we can't set non-local variables from a callback in python 2.x
+    bblayercalls = []
+
+    def handle_bblayers(varname, origvalue, op, newlines):
+        bblayercalls.append(varname)
+        updated = False
+        bblayers = [remove_trailing_sep(x) for x in origvalue.split()]
+        if removelayers:
+            for removelayer in removelayers:
+                matched = False
+                for layer in bblayers:
+                    if fnmatch.fnmatch(layer, removelayer):
+                        updated = True
+                        matched = True
+                        bblayers.remove(layer)
+                        break
+                if not matched:
+                    notremoved.append(removelayer)
+        if addlayers:
+            for addlayer in addlayers:
+                if addlayer not in bblayers:
+                    updated = True
+                    bblayers.append(addlayer)
+                else:
+                    notadded.append(addlayer)
+
+        if updated:
+            return (bblayers, None, 2, False)
+        else:
+            return (origvalue, None, 2, False)
+
+    edit_metadata_file(bblayers_conf, ['BBLAYERS'], handle_bblayers)
+
+    if not bblayercalls:
+        raise Exception('Unable to find BBLAYERS in %s' % bblayers_conf)
+
+    return (notadded, notremoved)
+
+
+def get_file_layer(filename, d):
+    """Determine the collection (as defined by a layer's layer.conf file) containing the specified file"""
+    collections = (d.getVar('BBFILE_COLLECTIONS', True) or '').split()
+    collection_res = {}
+    for collection in collections:
+        collection_res[collection] = d.getVar('BBFILE_PATTERN_%s' % collection, True) or ''
+
+    def path_to_layer(path):
+        # Use longest path so we handle nested layers
+        matchlen = 0
+        match = None
+        for collection, regex in collection_res.iteritems():
+            if len(regex) > matchlen and re.match(regex, path):
+                matchlen = len(regex)
+                match = collection
+        return match
+
+    result = None
+    bbfiles = (d.getVar('BBFILES', True) or '').split()
+    bbfilesmatch = False
+    for bbfilesentry in bbfiles:
+        if fnmatch.fnmatch(filename, bbfilesentry):
+            bbfilesmatch = True
+            result = path_to_layer(bbfilesentry)
+
+    if not bbfilesmatch:
+        # Probably a bbclass
+        result = path_to_layer(filename)
+
+    return result
+
+
+# Constant taken from http://linux.die.net/include/linux/prctl.h
+PR_SET_PDEATHSIG = 1
+
+class PrCtlError(Exception):
+    pass
+
+def signal_on_parent_exit(signame):
+    """
+    Trigger signame to be sent when the parent process dies
+    """
+    signum = getattr(signal, signame)
+    # http://linux.die.net/man/2/prctl
+    result = cdll['libc.so.6'].prctl(PR_SET_PDEATHSIG, signum)
+    if result != 0:
+        raise PrCtlError('prctl failed with error code %s' % result)