diff options
| author | Richard Purdie <richard@openedhand.com> | 2008-09-30 15:08:33 +0000 | 
|---|---|---|
| committer | Richard Purdie <richard@openedhand.com> | 2008-09-30 15:08:33 +0000 | 
| commit | c30eddb243e7e65f67f656e62848a033cf6f2e5c (patch) | |
| tree | 110dd95788b76f55d31cb8d30aac2de8400b6f4a /bitbake-dev/lib/bb/shell.py | |
| parent | 5ef0510474004eeb2ae8a99b64e2febb1920e077 (diff) | |
| download | openembedded-core-c30eddb243e7e65f67f656e62848a033cf6f2e5c.tar.gz openembedded-core-c30eddb243e7e65f67f656e62848a033cf6f2e5c.tar.bz2 openembedded-core-c30eddb243e7e65f67f656e62848a033cf6f2e5c.zip | |
Add bitbake-dev to allow ease of testing and development of bitbake trunk
git-svn-id: https://svn.o-hand.com/repos/poky/trunk@5337 311d38ba-8fff-0310-9ca6-ca027cbcb966
Diffstat (limited to 'bitbake-dev/lib/bb/shell.py')
| -rw-r--r-- | bitbake-dev/lib/bb/shell.py | 827 | 
1 files changed, 827 insertions, 0 deletions
| diff --git a/bitbake-dev/lib/bb/shell.py b/bitbake-dev/lib/bb/shell.py new file mode 100644 index 0000000000..34828fe425 --- /dev/null +++ b/bitbake-dev/lib/bb/shell.py @@ -0,0 +1,827 @@ +# 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 +########################################################################## + +try: +    set +except NameError: +    from sets import Set as set +import sys, os, readline, socket, httplib, urllib, commands, popen2, copy, shlex, Queue, fnmatch +from bb import data, parse, build, fatal, 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.keys(), globexpr ) +        if len( names ) == 0: names = [ globexpr ] +        print "SHELL: Building %s" % ' '.join( names ) + +        oldcmd = cooker.configuration.cmd +        cooker.configuration.cmd = cmd + +        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" % cooker.configuration.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, fnids: +            for fnid in fnids: +                print "ERROR: '%s' failed" % td.fn_index[fnid] +            last_exception = runqueue.TaskFailure + +        except build.EventException, e: +            print "ERROR: Couldn't build '%s'" % names +            last_exception = e + +        cooker.configuration.cmd = oldcmd + +    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 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 ) + +        oldcmd = cooker.configuration.cmd +        cooker.configuration.cmd = cmd + +        try: +            cooker.buildFile(bf) +        except parse.ParseError: +            print "ERROR: Unable to open or parse '%s'" % bf +        except build.EventException, e: +            print "ERROR: Couldn't build '%s'" % name +            last_exception = e + +        cooker.configuration.cmd = oldcmd +    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.bb_cache.cacheValidUpdate(bbfile) +        fromCache = cooker.bb_cache.loadData(bbfile, cooker.configuration.data) +        cooker.bb_cache.sync() +        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 +        allcmds = cmds.keys() +        allcmds.sort() +        for cmd in allcmds: +            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.keys(), globexpr ): print key +        elif what == "providers": +            self._checkParsed() +            for key in globfilter( cooker.status.pkg_pn.keys(), 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 >>newpackage,"""DESCRIPTION = "" +SECTION = "" +AUTHOR = "" +HOMEPAGE = "" +MAINTAINER = "" +LICENSE = "GPL" +PR = "r0" + +SRC_URI = "" + +#inherit base + +#do_configure() { +# +#} + +#do_compile() { +# +#} + +#do_stage() { +# +#} + +#do_install() { +# +#} +""" +            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() +        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 = cooker.bb_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.keys(): print key +        elif what == "providers": +            self._checkParsed() +            for key in cooker.status.providers.keys(): 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, "stage" ) +    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.keys(): +        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.keys() ] +                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." | 
