| # 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.") |