summaryrefslogtreecommitdiffstats
path: root/yocto-poky/bitbake/lib/bb/shell.py
diff options
context:
space:
mode:
authorPatrick Williams <patrick@stwcx.xyz>2015-09-15 14:41:29 -0500
committerPatrick Williams <patrick@stwcx.xyz>2015-09-15 14:41:29 -0500
commit21f9b84b4b729fbd7acbd465e7a3f726e4d20f91 (patch)
treeeb2d091d427ca0813b445509d59cc8e27e8ad25f /yocto-poky/bitbake/lib/bb/shell.py
parent101cef31e2bf54c678501155cd2106251acbd076 (diff)
parentc124f4f2e04dca16a428a76c89677328bc7bf908 (diff)
downloadtalos-openbmc-21f9b84b4b729fbd7acbd465e7a3f726e4d20f91.tar.gz
talos-openbmc-21f9b84b4b729fbd7acbd465e7a3f726e4d20f91.zip
Merge commit 'c124f4f2e04dca16a428a76c89677328bc7bf908' as 'yocto-poky'
Diffstat (limited to 'yocto-poky/bitbake/lib/bb/shell.py')
-rw-r--r--yocto-poky/bitbake/lib/bb/shell.py820
1 files changed, 820 insertions, 0 deletions
diff --git a/yocto-poky/bitbake/lib/bb/shell.py b/yocto-poky/bitbake/lib/bb/shell.py
new file mode 100644
index 000000000..1dd8d54bd
--- /dev/null
+++ b/yocto-poky/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.")
OpenPOWER on IntegriCloud