2 # ex:ts=4:sw=4:sts=4:et
3 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4 ##########################################################################
6 # Copyright (C) 2005-2006 Michael 'Mickey' Lauer <mickey@Vanille.de>
7 # Copyright (C) 2005-2006 Vanille Media
9 # This program is free software; you can redistribute it and/or modify it under
10 # the terms of the GNU General Public License as published by the Free Software
11 # Foundation; version 2 of the License.
13 # This program is distributed in the hope that it will be useful, but WITHOUT
14 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License along with
18 # this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 # Place, Suite 330, Boston, MA 02111-1307 USA.
21 ##########################################################################
24 # * Holger Freyther <zecke@handhelds.org>
25 # * Justin Patrin <papercrane@reversefold.com>
27 ##########################################################################
33 * list defined tasks per package
36 * command to reparse just one (or more) bbfile(s)
37 * automatic check if reparsing is necessary (inotify?)
38 * frontend for bb file manipulation
39 * more shell-like features:
40 - output control, i.e. pipe output into grep, sort, etc.
41 - job control, i.e. bring running commands into background and foreground
42 * start parsing in background right after startup
46 * force doesn't always work
47 * readline completion for commands with more than one parameters
51 ##########################################################################
52 # Import and setup global variables
53 ##########################################################################
58 from sets import Set as set
59 import sys, os, readline, socket, httplib, urllib, commands, popen2, copy, shlex, Queue, fnmatch
60 from bb import data, parse, build, fatal, cache, taskdata, runqueue, providers as Providers
62 __version__ = "0.5.3.1"
63 __credits__ = """BitBake Shell Version %s (C) 2005 Michael 'Mickey' Lauer <mickey@Vanille.de>
64 Type 'help' for more information, press CTRL-D to exit.""" % __version__
67 leave_mainloop = False
72 debug = os.environ.get( "BBSHELL_DEBUG", "" )
74 ##########################################################################
75 # Class BitBakeShellCommands
76 ##########################################################################
78 class BitBakeShellCommands:
79 """This class contains the valid commands for the shell"""
81 def __init__( self, shell ):
82 """Register all the commands"""
84 for attr in BitBakeShellCommands.__dict__:
85 if not attr.startswith( "_" ):
86 if attr.endswith( "_" ):
87 command = attr[:-1].lower()
89 command = attr[:].lower()
90 method = getattr( BitBakeShellCommands, attr )
91 debugOut( "registering command '%s'" % command )
92 # scan number of arguments
93 usage = getattr( method, "usage", "" )
95 numArgs = len( usage.split() )
98 shell.registerCommand( command, method, numArgs, "%s %s" % ( command, usage ), method.__doc__ )
100 def _checkParsed( self ):
102 print "SHELL: This command needs to parse bbfiles..."
105 def _findProvider( self, item ):
107 preferred = data.getVar( "PREFERRED_PROVIDER_%s" % item, cooker.configuration.data, 1 )
108 if not preferred: preferred = item
110 lv, lf, pv, pf = Providers.findBestProvider(preferred, cooker.configuration.data, cooker.status, cooker.build_cache_fail)
112 if item in cooker.status.providers:
113 pf = cooker.status.providers[item][0]
118 def alias( self, params ):
119 """Register a new name for a command"""
122 print "ERROR: Command '%s' not known" % old
124 cmds[new] = cmds[old]
126 alias.usage = "<alias> <command>"
128 def buffer( self, params ):
129 """Dump specified output buffer"""
131 print self._shell.myout.buffer( int( index ) )
132 buffer.usage = "<index>"
134 def buffers( self, params ):
135 """Show the available output buffers"""
136 commands = self._shell.myout.bufferedCommands()
138 print "SHELL: No buffered commands available yet. Start doing something."
140 print "="*35, "Available Output Buffers", "="*27
141 for index, cmd in enumerate( commands ):
142 print "| %s %s" % ( str( index ).ljust( 3 ), cmd )
145 def build( self, params, cmd = "build" ):
146 """Build a providee"""
149 names = globfilter( cooker.status.pkg_pn.keys(), globexpr )
150 if len( names ) == 0: names = [ globexpr ]
151 print "SHELL: Building %s" % ' '.join( names )
153 oldcmd = cooker.configuration.cmd
154 cooker.configuration.cmd = cmd
155 cooker.build_cache = []
156 cooker.build_cache_fail = []
158 td = taskdata.TaskData(cooker.configuration.abort)
163 td.add_provider(cooker.configuration.data, cooker.status, name)
164 providers = td.get_provider(name)
166 if len(providers) == 0:
167 raise Providers.NoProvider
169 tasks.append([name, "do_%s" % cooker.configuration.cmd])
171 td.add_unresolved(cooker.configuration.data, cooker.status)
173 rq = runqueue.RunQueue()
174 rq.prepare_runqueue(cooker.configuration.data, cooker.status, td, tasks)
175 rq.execute_runqueue(cooker, cooker.configuration.data, cooker.status, td, tasks)
177 except Providers.NoProvider:
178 print "ERROR: No Provider"
179 global last_exception
180 last_exception = Providers.NoProvider
182 except runqueue.TaskFailure, (fnid, fn, taskname):
183 print "ERROR: '%s, %s' failed" % (fn, taskname)
184 global last_exception
185 last_exception = runqueue.TaskFailure
187 except build.EventException, e:
188 print "ERROR: Couldn't build '%s'" % names
189 global last_exception
192 cooker.configuration.cmd = oldcmd
194 build.usage = "<providee>"
196 def clean( self, params ):
197 """Clean a providee"""
198 self.build( params, "clean" )
199 clean.usage = "<providee>"
201 def compile( self, params ):
202 """Execute 'compile' on a providee"""
203 self.build( params, "compile" )
204 compile.usage = "<providee>"
206 def configure( self, params ):
207 """Execute 'configure' on a providee"""
208 self.build( params, "configure" )
209 configure.usage = "<providee>"
211 def edit( self, params ):
212 """Call $EDITOR on a providee"""
214 bbfile = self._findProvider( name )
215 if bbfile is not None:
216 os.system( "%s %s" % ( os.environ.get( "EDITOR", "vi" ), bbfile ) )
218 print "ERROR: Nothing provides '%s'" % name
219 edit.usage = "<providee>"
221 def environment( self, params ):
222 """Dump out the outer BitBake environment (see bbread)"""
223 data.emit_env(sys.__stdout__, cooker.configuration.data, True)
225 def exit_( self, params ):
226 """Leave the BitBake Shell"""
227 debugOut( "setting leave_mainloop to true" )
228 global leave_mainloop
229 leave_mainloop = True
231 def fetch( self, params ):
232 """Fetch a providee"""
233 self.build( params, "fetch" )
234 fetch.usage = "<providee>"
236 def fileBuild( self, params, cmd = "build" ):
237 """Parse and build a .bb file"""
239 bf = completeFilePath( name )
240 print "SHELL: Calling '%s' on '%s'" % ( cmd, bf )
242 oldcmd = cooker.configuration.cmd
243 cooker.configuration.cmd = cmd
244 cooker.build_cache = []
245 cooker.build_cache_fail = []
247 thisdata = copy.deepcopy( initdata )
248 # Caution: parse.handle modifies thisdata, hence it would
249 # lead to pollution cooker.configuration.data, which is
250 # why we use it on a safe copy we obtained from cooker right after
251 # parsing the initial *.conf files
253 bbfile_data = parse.handle( bf, thisdata )
254 except parse.ParseError:
255 print "ERROR: Unable to open or parse '%s'" % bf
257 item = data.getVar('PN', bbfile_data, 1)
258 data.setVar( "_task_cache", [], bbfile_data ) # force
260 cooker.tryBuildPackage( os.path.abspath( bf ), item, cmd, bbfile_data, True )
261 except build.EventException, e:
262 print "ERROR: Couldn't build '%s'" % name
263 global last_exception
266 cooker.configuration.cmd = oldcmd
267 fileBuild.usage = "<bbfile>"
269 def fileClean( self, params ):
270 """Clean a .bb file"""
271 self.fileBuild( params, "clean" )
272 fileClean.usage = "<bbfile>"
274 def fileEdit( self, params ):
275 """Call $EDITOR on a .bb file"""
277 os.system( "%s %s" % ( os.environ.get( "EDITOR", "vi" ), completeFilePath( name ) ) )
278 fileEdit.usage = "<bbfile>"
280 def fileRebuild( self, params ):
281 """Rebuild (clean & build) a .bb file"""
282 self.fileBuild( params, "rebuild" )
283 fileRebuild.usage = "<bbfile>"
285 def fileReparse( self, params ):
286 """(re)Parse a bb file"""
288 print "SHELL: Parsing '%s'" % bbfile
289 parse.update_mtime( bbfile )
290 cooker.bb_cache.cacheValidUpdate(bbfile)
291 fromCache = cooker.bb_cache.loadData(bbfile, cooker.configuration.data)
292 cooker.bb_cache.sync()
293 if False: #fromCache:
294 print "SHELL: File has not been updated, not reparsing"
296 print "SHELL: Parsed"
297 fileReparse.usage = "<bbfile>"
299 def abort( self, params ):
300 """Toggle abort task execution flag (see bitbake -k)"""
301 cooker.configuration.abort = not cooker.configuration.abort
302 print "SHELL: Abort Flag is now '%s'" % repr( cooker.configuration.abort )
304 def force( self, params ):
305 """Toggle force task execution flag (see bitbake -f)"""
306 cooker.configuration.force = not cooker.configuration.force
307 print "SHELL: Force Flag is now '%s'" % repr( cooker.configuration.force )
309 def help( self, params ):
310 """Show a comprehensive list of commands and their purpose"""
311 print "="*30, "Available Commands", "="*30
312 allcmds = cmds.keys()
315 function,numparams,usage,helptext = cmds[cmd]
316 print "| %s | %s" % (usage.ljust(30), helptext)
319 def lastError( self, params ):
320 """Show the reason or log that was produced by the last BitBake event exception"""
321 if last_exception is None:
322 print "SHELL: No Errors yet (Phew)..."
324 reason, event = last_exception.args
325 print "SHELL: Reason for the last error: '%s'" % reason
327 msg, filename = reason.split( ':' )
328 filename = filename.strip()
329 print "SHELL: Dumping log file for last error:"
331 print open( filename ).read()
333 print "ERROR: Couldn't open '%s'" % filename
335 def match( self, params ):
336 """Dump all files or providers matching a glob expression"""
337 what, globexpr = params
340 for key in globfilter( cooker.status.pkg_fn.keys(), globexpr ): print key
341 elif what == "providers":
343 for key in globfilter( cooker.status.pkg_pn.keys(), globexpr ): print key
345 print "Usage: match %s" % self.print_.usage
346 match.usage = "<files|providers> <glob>"
348 def new( self, params ):
349 """Create a new .bb file and open the editor"""
350 dirname, filename = params
351 packages = '/'.join( data.getVar( "BBFILES", cooker.configuration.data, 1 ).split('/')[:-2] )
352 fulldirname = "%s/%s" % ( packages, dirname )
354 if not os.path.exists( fulldirname ):
355 print "SHELL: Creating '%s'" % fulldirname
356 os.mkdir( fulldirname )
357 if os.path.exists( fulldirname ) and os.path.isdir( fulldirname ):
358 if os.path.exists( "%s/%s" % ( fulldirname, filename ) ):
359 print "SHELL: ERROR: %s/%s already exists" % ( fulldirname, filename )
361 print "SHELL: Creating '%s/%s'" % ( fulldirname, filename )
362 newpackage = open( "%s/%s" % ( fulldirname, filename ), "w" )
363 print >>newpackage,"""DESCRIPTION = ""
392 os.system( "%s %s/%s" % ( os.environ.get( "EDITOR" ), fulldirname, filename ) )
393 new.usage = "<directory> <filename>"
395 def pasteBin( self, params ):
396 """Send a command + output buffer to the pastebin at http://rafb.net/paste"""
398 contents = self._shell.myout.buffer( int( index ) )
399 sendToPastebin( "output of " + params[0], contents )
400 pasteBin.usage = "<index>"
402 def pasteLog( self, params ):
403 """Send the last event exception error log (if there is one) to http://rafb.net/paste"""
404 if last_exception is None:
405 print "SHELL: No Errors yet (Phew)..."
407 reason, event = last_exception.args
408 print "SHELL: Reason for the last error: '%s'" % reason
410 msg, filename = reason.split( ':' )
411 filename = filename.strip()
412 print "SHELL: Pasting log file to pastebin..."
414 file = open( filename ).read()
415 sendToPastebin( "contents of " + filename, file )
417 def patch( self, params ):
418 """Execute 'patch' command on a providee"""
419 self.build( params, "patch" )
420 patch.usage = "<providee>"
422 def parse( self, params ):
423 """(Re-)parse .bb files and calculate the dependency graph"""
424 cooker.status = cache.CacheData()
425 ignore = data.getVar("ASSUME_PROVIDED", cooker.configuration.data, 1) or ""
426 cooker.status.ignored_dependencies = set( ignore.split() )
427 cooker.handleCollections( data.getVar("BBFILE_COLLECTIONS", cooker.configuration.data, 1) )
429 (filelist, masked) = cooker.collect_bbfiles()
430 cooker.parse_bbfiles(filelist, masked, cooker.myProgressCallback)
431 cooker.buildDepgraph()
436 def reparse( self, params ):
437 """(re)Parse a providee's bb file"""
438 bbfile = self._findProvider( params[0] )
439 if bbfile is not None:
440 print "SHELL: Found bbfile '%s' for '%s'" % ( bbfile, params[0] )
441 self.fileReparse( [ bbfile ] )
443 print "ERROR: Nothing provides '%s'" % params[0]
444 reparse.usage = "<providee>"
446 def getvar( self, params ):
447 """Dump the contents of an outer BitBake environment variable"""
449 value = data.getVar( var, cooker.configuration.data, 1 )
451 getvar.usage = "<variable>"
453 def peek( self, params ):
454 """Dump contents of variable defined in providee's metadata"""
456 bbfile = self._findProvider( name )
457 if bbfile is not None:
458 the_data = cooker.bb_cache.loadDataFull(bbfile, cooker.configuration.data)
459 value = the_data.getVar( var, 1 )
462 print "ERROR: Nothing provides '%s'" % name
463 peek.usage = "<providee> <variable>"
465 def poke( self, params ):
466 """Set contents of variable defined in providee's metadata"""
467 name, var, value = params
468 bbfile = self._findProvider( name )
469 if bbfile is not None:
470 print "ERROR: Sorry, this functionality is currently broken"
471 #d = cooker.pkgdata[bbfile]
472 #data.setVar( var, value, d )
474 # mark the change semi persistant
475 #cooker.pkgdata.setDirty(bbfile, d)
478 print "ERROR: Nothing provides '%s'" % name
479 poke.usage = "<providee> <variable> <value>"
481 def print_( self, params ):
482 """Dump all files or providers"""
486 for key in cooker.status.pkg_fn.keys(): print key
487 elif what == "providers":
489 for key in cooker.status.providers.keys(): print key
491 print "Usage: print %s" % self.print_.usage
492 print_.usage = "<files|providers>"
494 def python( self, params ):
495 """Enter the expert mode - an interactive BitBake Python Interpreter"""
496 sys.ps1 = "EXPERT BB>>> "
497 sys.ps2 = "EXPERT BB... "
499 interpreter = code.InteractiveConsole( dict( globals() ) )
500 interpreter.interact( "SHELL: Expert Mode - BitBake Python %s\nType 'help' for more information, press CTRL-D to switch back to BBSHELL." % sys.version )
502 def showdata( self, params ):
503 """Execute 'showdata' on a providee"""
504 self.build( params, "showdata" )
505 showdata.usage = "<providee>"
507 def setVar( self, params ):
508 """Set an outer BitBake environment variable"""
510 data.setVar( var, value, cooker.configuration.data )
512 setVar.usage = "<variable> <value>"
514 def rebuild( self, params ):
515 """Clean and rebuild a .bb file or a providee"""
516 self.build( params, "clean" )
517 self.build( params, "build" )
518 rebuild.usage = "<providee>"
520 def shell( self, params ):
521 """Execute a shell command and dump the output"""
523 print commands.getoutput( " ".join( params ) )
524 shell.usage = "<...>"
526 def stage( self, params ):
527 """Execute 'stage' on a providee"""
528 self.build( params, "stage" )
529 stage.usage = "<providee>"
531 def status( self, params ):
532 """<just for testing>"""
534 print "build cache = '%s'" % cooker.build_cache
535 print "build cache fail = '%s'" % cooker.build_cache_fail
536 print "building list = '%s'" % cooker.building_list
537 print "build path = '%s'" % cooker.build_path
538 print "consider_msgs_cache = '%s'" % cooker.consider_msgs_cache
539 print "build stats = '%s'" % cooker.stats
540 if last_exception is not None: print "last_exception = '%s'" % repr( last_exception.args )
541 print "memory output contents = '%s'" % self._shell.myout._buffer
543 def test( self, params ):
544 """<just for testing>"""
545 print "testCommand called with '%s'" % params
547 def unpack( self, params ):
548 """Execute 'unpack' on a providee"""
549 self.build( params, "unpack" )
550 unpack.usage = "<providee>"
552 def which( self, params ):
553 """Computes the providers for a given providee"""
558 preferred = data.getVar( "PREFERRED_PROVIDER_%s" % item, cooker.configuration.data, 1 )
559 if not preferred: preferred = item
562 lv, lf, pv, pf = Providers.findBestProvider(preferred, cooker.configuration.data, cooker.status,
563 cooker.build_cache_fail)
565 lv, lf, pv, pf = (None,)*4
568 providers = cooker.status.providers[item]
570 print "SHELL: ERROR: Nothing provides", preferred
572 for provider in providers:
573 if provider == pf: provider = " (***) %s" % provider
574 else: provider = " %s" % provider
576 which.usage = "<providee>"
578 ##########################################################################
579 # Common helper functions
580 ##########################################################################
582 def completeFilePath( bbfile ):
583 """Get the complete bbfile path"""
584 if not cooker.status.pkg_fn: return bbfile
585 for key in cooker.status.pkg_fn.keys():
586 if key.endswith( bbfile ):
590 def sendToPastebin( desc, content ):
591 """Send content to http://oe.pastebin.com"""
593 mydata["lang"] = "Plain Text"
594 mydata["desc"] = desc
595 mydata["cvt_tabs"] = "No"
596 mydata["nick"] = "%s@%s" % ( os.environ.get( "USER", "unknown" ), socket.gethostname() or "unknown" )
597 mydata["text"] = content
598 params = urllib.urlencode( mydata )
599 headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain"}
602 conn = httplib.HTTPConnection( "%s:80" % host )
603 conn.request("POST", "/paste/paste.php", params, headers )
605 response = conn.getresponse()
608 if response.status == 302:
609 location = response.getheader( "location" ) or "unknown"
610 print "SHELL: Pasted to http://%s%s" % ( host, location )
612 print "ERROR: %s %s" % ( response.status, response.reason )
614 def completer( text, state ):
615 """Return a possible readline completion"""
616 debugOut( "completer called with text='%s', state='%d'" % ( text, state ) )
619 line = readline.get_line_buffer()
622 # we are in second (or more) argument
623 if line[0] in cmds and hasattr( cmds[line[0]][0], "usage" ): # known command and usage
624 u = getattr( cmds[line[0]][0], "usage" ).split()[0]
625 if u == "<variable>":
626 allmatches = cooker.configuration.data.keys()
627 elif u == "<bbfile>":
628 if cooker.status.pkg_fn is None: allmatches = [ "(No Matches Available. Parsed yet?)" ]
629 else: allmatches = [ x.split("/")[-1] for x in cooker.status.pkg_fn.keys() ]
630 elif u == "<providee>":
631 if cooker.status.pkg_fn is None: allmatches = [ "(No Matches Available. Parsed yet?)" ]
632 else: allmatches = cooker.status.providers.iterkeys()
633 else: allmatches = [ "(No tab completion available for this command)" ]
634 else: allmatches = [ "(No tab completion available for this command)" ]
636 # we are in first argument
637 allmatches = cmds.iterkeys()
639 completer.matches = [ x for x in allmatches if x[:len(text)] == text ]
640 #print "completer.matches = '%s'" % completer.matches
641 if len( completer.matches ) > state:
642 return completer.matches[state]
646 def debugOut( text ):
648 sys.stderr.write( "( %s )\n" % text )
650 def columnize( alist, width = 80 ):
652 A word-wrap function that preserves existing line breaks
653 and most spaces in the text. Expects that existing line
654 breaks are posix newlines (\n).
656 return reduce(lambda line, word, width=width: '%s%s%s' %
658 ' \n'[(len(line[line.rfind('\n')+1:])
659 + len(word.split('\n',1)[0]
665 def globfilter( names, pattern ):
666 return fnmatch.filter( names, pattern )
668 ##########################################################################
670 ##########################################################################
673 """File-like output class buffering the output of the last 10 commands"""
674 def __init__( self, delegate ):
675 self.delegate = delegate
680 def startCommand( self, command ):
681 self._command = command
683 def endCommand( self ):
684 if self._command is not None:
685 if len( self._buffer ) == 10: del self._buffer[0]
686 self._buffer.append( ( self._command, self.text ) )
687 def removeLast( self ):
689 del self._buffer[ len( self._buffer ) - 1 ]
692 def lastBuffer( self ):
694 return self._buffer[ len( self._buffer ) -1 ][1]
695 def bufferedCommands( self ):
696 return [ cmd for cmd, output in self._buffer ]
697 def buffer( self, i ):
698 if i < len( self._buffer ):
699 return "BB>> %s\n%s" % ( self._buffer[i][0], "".join( self._buffer[i][1] ) )
700 else: return "ERROR: Invalid buffer number. Buffer needs to be in (0, %d)" % ( len( self._buffer ) - 1 )
701 def write( self, text ):
702 if self._command is not None and text != "BB>> ": self.text.append( text )
703 if self.delegate is not None: self.delegate.write( text )
705 return self.delegate.flush()
707 return self.delegate.fileno()
709 return self.delegate.isatty()
711 ##########################################################################
713 ##########################################################################
717 def __init__( self ):
718 """Register commands and set up readline"""
719 self.commandQ = Queue.Queue()
720 self.commands = BitBakeShellCommands( self )
721 self.myout = MemoryOutput( sys.stdout )
722 self.historyfilename = os.path.expanduser( "~/.bbsh_history" )
723 self.startupfilename = os.path.expanduser( "~/.bbsh_startup" )
725 readline.set_completer( completer )
726 readline.set_completer_delims( " " )
727 readline.parse_and_bind("tab: complete")
730 readline.read_history_file( self.historyfilename )
732 pass # It doesn't exist yet.
736 # save initial cooker configuration (will be reused in file*** commands)
738 initdata = copy.deepcopy( cooker.configuration.data )
741 """Write readline history and clean up resources"""
742 debugOut( "writing command history" )
744 readline.write_history_file( self.historyfilename )
746 print "SHELL: Unable to save command history"
748 def registerCommand( self, command, function, numparams = 0, usage = "", helptext = "" ):
749 """Register a command"""
750 if usage == "": usage = command
751 if helptext == "": helptext = function.__doc__ or "<not yet documented>"
752 cmds[command] = ( function, numparams, usage, helptext )
754 def processCommand( self, command, params ):
755 """Process a command. Check number of params and print a usage string, if appropriate"""
756 debugOut( "processing command '%s'..." % command )
758 function, numparams, usage, helptext = cmds[command]
760 print "SHELL: ERROR: '%s' command is not a valid command." % command
761 self.myout.removeLast()
763 if (numparams != -1) and (not len( params ) == numparams):
764 print "Usage: '%s'" % usage
767 result = function( self.commands, params )
768 debugOut( "result was '%s'" % result )
770 def processStartupFile( self ):
771 """Read and execute all commands found in $HOME/.bbsh_startup"""
772 if os.path.exists( self.startupfilename ):
773 startupfile = open( self.startupfilename, "r" )
774 for cmdline in startupfile:
775 debugOut( "processing startup line '%s'" % cmdline )
779 print "ERROR: '|' in startup file is not allowed. Ignoring line"
781 self.commandQ.put( cmdline.strip() )
784 """The main command loop"""
785 while not leave_mainloop:
787 if self.commandQ.empty():
788 sys.stdout = self.myout.delegate
789 cmdline = raw_input( "BB>> " )
790 sys.stdout = self.myout
792 cmdline = self.commandQ.get()
794 allCommands = cmdline.split( ';' )
795 for command in allCommands:
798 # special case for expert mode
799 if command == 'python':
800 sys.stdout = self.myout.delegate
801 self.processCommand( command, "" )
802 sys.stdout = self.myout
804 self.myout.startCommand( command )
805 if '|' in command: # disable output
806 command, pipecmd = command.split( '|' )
807 delegate = self.myout.delegate
808 self.myout.delegate = None
809 tokens = shlex.split( command, True )
810 self.processCommand( tokens[0], tokens[1:] or "" )
811 self.myout.endCommand()
812 if pipecmd is not None: # restore output
813 self.myout.delegate = delegate
815 pipe = popen2.Popen4( pipecmd )
816 pipe.tochild.write( "\n".join( self.myout.lastBuffer() ) )
818 sys.stdout.write( pipe.fromchild.read() )
823 except KeyboardInterrupt:
826 ##########################################################################
827 # Start function - called from the BitBake command line utility
828 ##########################################################################
830 def start( aCooker ):
833 bbshell = BitBakeShell()
834 bbshell.processStartupFile()
838 if __name__ == "__main__":
839 print "SHELL: Sorry, this program should only be called by BitBake."