- add 'alias' command to allow shortcuts for commands
[vuplus_bitbake] / lib / bb / shell.py
1 #!/usr/bin/env python
2 # ex:ts=4:sw=4:sts=4:et
3 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4 ##########################################################################
5 #
6 # Copyright (C) 2005 Michael 'Mickey' Lauer <mickey@Vanille.de>, Vanille Media
7 #
8 # This program is free software; you can redistribute it and/or modify it under
9 # the terms of the GNU General Public License as published by the Free Software
10 # Foundation; version 2 of the License.
11 #
12 # This program is distributed in the hope that it will be useful, but WITHOUT
13 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along with
17 # this program; if not, write to the Free Software Foundation, Inc., 59 Temple
18 # Place, Suite 330, Boston, MA 02111-1307 USA.
19 #
20 ##########################################################################
21
22 """
23 BitBake Shell
24
25 IDEAS:
26     * list defined tasks per package
27     * list classes
28     * toggle force
29     * command to reparse just one (or more) bbfile(s)
30     * automatic check if reparsing is necessary (inotify?)
31     * frontend for bb file manipulation
32     * more shell-like features:
33         - shell lexer (shlex)
34         - output control, i.e. pipe output into grep, sort, etc.
35         - job control, i.e. bring running commands into background and foreground
36         - wildcards for commands, i.e. build *opie*
37     * start parsing in background right after startup
38     * print variable from package data
39     * ncurses interface
40     * read some initial commands from startup file (batch)
41
42 PROBLEMS:
43     * force doesn't always work
44     * poke doesn't work at all (outcommented atm.)
45     * readline completion for commands with more than one parameters
46
47 """
48
49 ##########################################################################
50 # Import and setup global variables
51 ##########################################################################
52
53 try:
54     set
55 except NameError:
56     from sets import Set as set
57 import sys, os, imp, readline, socket, httplib, urllib, commands, popen2
58 imp.load_source( "bitbake", os.path.dirname( sys.argv[0] )+"/bitbake" )
59 from bb import data, parse, build, make, fatal
60
61 __version__ = "0.5.0"
62 __credits__ = """BitBake Shell Version %s (C) 2005 Michael 'Mickey' Lauer <mickey@Vanille.de>
63 Type 'help' for more information, press CTRL-D to exit.""" % __version__
64
65 cmds = {}
66 leave_mainloop = False
67 last_exception = None
68 cooker = None
69 parsed = False
70 debug = os.environ.get( "BBSHELL_DEBUG", "" )
71
72 ##########################################################################
73 # Class BitBakeShellCommands
74 ##########################################################################
75
76 class BitBakeShellCommands:
77     """This class contains the valid commands for the shell"""
78
79     def __init__( self, shell ):
80         """Register all the commands"""
81         self._shell = shell
82         for attr in BitBakeShellCommands.__dict__:
83             if not attr.startswith( "_" ):
84                 if attr.endswith( "_" ):
85                     command = attr[:-1].lower()
86                 else:
87                     command = attr[:].lower()
88                 method = getattr( BitBakeShellCommands, attr )
89                 debugOut( "registering command '%s'" % command )
90                 # scan number of arguments
91                 usage = getattr( method, "usage", "" )
92                 if usage != "<...>":
93                     numArgs = len( usage.split() )
94                 else:
95                     numArgs = -1
96                 shell.registerCommand( command, method, numArgs, "%s %s" % ( command, usage ), method.__doc__ )
97
98     def _checkParsed( self ):
99         if not parsed:
100             print "SHELL: This command needs to parse bbfiles..."
101             self.parse( None )
102
103     def _findProvider( self, item ):
104         self._checkParsed()
105         preferred = data.getVar( "PREFERRED_PROVIDER_%s" % item, make.cfg, 1 )
106         if not preferred: preferred = item
107         try:
108             lv, lf, pv, pf = cooker.findBestProvider( preferred )
109         except KeyError:
110             lv, lf, pv, pf = (None,)*4
111         return pf
112
113     def alias( self, params ):
114         """Register a new name for a command"""
115         new, old = params
116         if not old in cmds:
117             print "ERROR: Command '%s' not known" % old
118         else:
119             cmds[new] = cmds[old]
120             print "OK"
121     alias.usage = "<alias> <command>"
122
123     def buffer( self, params ):
124         """Dump specified output buffer"""
125         index = params[0]
126         print self._shell.myout.buffer( int( index ) )
127     buffer.usage = "<index>"
128
129     def buffers( self, params ):
130         """Show the available output buffers"""
131         commands = self._shell.myout.bufferedCommands()
132         if not commands:
133             print "SHELL: No buffered commands available yet. Start doing something."
134         else:
135             print "="*35, "Available Output Buffers", "="*27
136             for index, cmd in enumerate( commands ):
137                 print "| %s %s" % ( str( index ).ljust( 3 ), cmd )
138             print "="*88
139
140     def build( self, params, cmd = "build" ):
141         """Build a providee"""
142         name = params[0]
143
144         oldcmd = make.options.cmd
145         make.options.cmd = cmd
146         cooker.build_cache = []
147         cooker.build_cache_fail = []
148
149         self._checkParsed()
150
151         try:
152             cooker.buildProvider( name )
153         except build.EventException, e:
154             print "ERROR: Couldn't build '%s'" % name
155             global last_exception
156             last_exception = e
157
158         make.options.cmd = oldcmd
159     build.usage = "<providee>"
160
161     def clean( self, params ):
162         """Clean a providee"""
163         self.build( params, "clean" )
164     clean.usage = "<providee>"
165
166     def compile( self, params ):
167         """Execute 'compile' on a providee"""
168         self.build( params, "compile" )
169     compile.usage = "<providee>"
170
171     def configure( self, params ):
172         """Execute 'configure' on a providee"""
173         self.build( params, "configure" )
174     configure.usage = "<providee>"
175
176     def edit( self, params ):
177         """Call $EDITOR on a providee"""
178         name = params[0]
179         bbfile = self._findProvider( name )
180         if bbfile is not None:
181             os.system( "%s %s" % ( os.environ.get( "EDITOR", "vi" ), bbfile ) )
182         else:
183             print "ERROR: Nothing provides '%s'" % name
184     edit.usage = "<providee>"
185
186     def environment( self, params ):
187         """Dump out the outer BitBake environment (see bbread)"""
188         data.emit_env(sys.__stdout__, make.cfg, True)
189
190     def exit_( self, params ):
191         """Leave the BitBake Shell"""
192         debugOut( "setting leave_mainloop to true" )
193         global leave_mainloop
194         leave_mainloop = True
195
196     def fetch( self, params ):
197         """Fetch a providee"""
198         self.build( params, "fetch" )
199     fetch.usage = "<providee>"
200
201     def fileBuild( self, params, cmd = "build" ):
202         """Parse and build a .bb file"""
203         name = params[0]
204         bf = completeFilePath( name )
205         print "SHELL: Calling '%s' on '%s'" % ( cmd, bf )
206
207         oldcmd = make.options.cmd
208         make.options.cmd = cmd
209         cooker.build_cache = []
210         cooker.build_cache_fail = []
211
212         try:
213             bbfile_data = parse.handle( bf, make.cfg )
214         except parse.ParseError:
215             print "ERROR: Unable to open or parse '%s'" % bf
216         else:
217             item = data.getVar('PN', bbfile_data, 1)
218             data.setVar( "_task_cache", [], bbfile_data ) # force
219             try:
220                 cooker.tryBuildPackage( os.path.abspath( bf ), item, bbfile_data )
221             except build.EventException, e:
222                 print "ERROR: Couldn't build '%s'" % name
223                 global last_exception
224                 last_exception = e
225
226         make.options.cmd = oldcmd
227     fileBuild.usage = "<bbfile>"
228
229     def fileClean( self, params ):
230         """Clean a .bb file"""
231         self.fileBuild( params, "clean" )
232     fileClean.usage = "<bbfile>"
233
234     def fileEdit( self, params ):
235         """Call $EDITOR on a .bb file"""
236         name = params[0]
237         os.system( "%s %s" % ( os.environ.get( "EDITOR", "vi" ), completeFilePath( name ) ) )
238     fileEdit.usage = "<bbfile>"
239
240     def fileRebuild( self, params ):
241         """Rebuild (clean & build) a .bb file"""
242         self.fileClean( params )
243         self.fileBuild( params )
244     fileRebuild.usage = "<bbfile>"
245
246     def force( self, params ):
247         """Toggle force task execution flag (see bitbake -f)"""
248         make.options.force = not make.options.force
249         print "SHELL: Force Flag is now '%s'" % repr( make.options.force )
250
251     def help( self, params ):
252         """Show a comprehensive list of commands and their purpose"""
253         print "="*30, "Available Commands", "="*30
254         allcmds = cmds.keys()
255         allcmds.sort()
256         for cmd in allcmds:
257             function,numparams,usage,helptext = cmds[cmd]
258             print "| %s | %s" % (usage.ljust(30), helptext)
259         print "="*78
260
261     def lastError( self, params ):
262         """Show the reason or log that was produced by the last BitBake event exception"""
263         if last_exception is None:
264             print "SHELL: No Errors yet (Phew)..."
265         else:
266             reason, event = last_exception.args
267             print "SHELL: Reason for the last error: '%s'" % reason
268             if ':' in reason:
269                 msg, filename = reason.split( ':' )
270                 filename = filename.strip()
271                 print "SHELL: Dumping log file for last error:"
272                 try:
273                     print open( filename ).read()
274                 except IOError:
275                     print "ERROR: Couldn't open '%s'" % filename
276
277     def new( self, params ):
278         """Create a new .bb file and open the editor"""
279         dirname, filename = params
280         packages = '/'.join( data.getVar( "BBFILES", make.cfg, 1 ).split('/')[:-2] )
281         fulldirname = "%s/%s" % ( packages, dirname )
282
283         if not os.path.exists( fulldirname ):
284             print "SHELL: Creating '%s'" % fulldirname
285             os.mkdir( fulldirname )
286         if os.path.exists( fulldirname ) and os.path.isdir( fulldirname ):
287             if os.path.exists( "%s/%s" % ( fulldirname, filename ) ):
288                 print "SHELL: ERROR: %s/%s already exists" % ( fulldirname, filename )
289                 return False
290             print "SHELL: Creating '%s/%s'" % ( fulldirname, filename )
291             newpackage = open( "%s/%s" % ( fulldirname, filename ), "w" )
292             print >>newpackage,"""DESCRIPTION = ""
293 SECTION = ""
294 AUTHOR = ""
295 HOMEPAGE = ""
296 MAINTAINER = ""
297 LICENSE = "GPL"
298 PR = "r0"
299
300 SRC_URI = ""
301
302 #inherit base
303
304 #do_configure() {
305 #
306 #}
307
308 #do_compile() {
309 #
310 #}
311
312 #do_stage() {
313 #
314 #}
315
316 #do_install() {
317 #
318 #}
319 """
320             newpackage.close()
321             os.system( "%s %s/%s" % ( os.environ.get( "EDITOR" ), fulldirname, filename ) )
322     new.usage = "<directory> <filename>"
323
324     def pasteBin( self, params ):
325         """Send a command + output buffer to http://pastebin.com"""
326         index = params[0]
327         contents = self._shell.myout.buffer( int( index ) )
328         status, error, location = sendToPastebin( contents )
329         if status == 302:
330             print "SHELL: Pasted to %s" % location
331         else:
332             print "ERROR: %s %s" % ( status, error )
333     pasteBin.usage = "<index>"
334
335     def pasteLog( self, params ):
336         """Send the last event exception error log (if there is one) to http://pastebin.com"""
337         if last_exception is None:
338             print "SHELL: No Errors yet (Phew)..."
339         else:
340             reason, event = last_exception.args
341             print "SHELL: Reason for the last error: '%s'" % reason
342             if ':' in reason:
343                 msg, filename = reason.split( ':' )
344                 filename = filename.strip()
345                 print "SHELL: Pasting log file to pastebin..."
346
347                 status, error, location = sendToPastebin( open( filename ).read() )
348
349                 if status == 302:
350                     print "SHELL: Pasted to %s" % location
351                 else:
352                     print "ERROR: %s %s" % ( status, error )
353
354     def patch( self, params ):
355         """Execute 'patch' command on a providee"""
356         self.build( params, "patch" )
357     patch.usage = "<providee>"
358
359     def parse( self, params ):
360         """(Re-)parse .bb files and calculate the dependency graph"""
361         cooker.status = cooker.ParsingStatus()
362         ignore = data.getVar("ASSUME_PROVIDED", make.cfg, 1) or ""
363         cooker.status.ignored_dependencies = set( ignore.split() )
364         cooker.handleCollections( data.getVar("BBFILE_COLLECTIONS", make.cfg, 1) )
365
366         make.collect_bbfiles( cooker.myProgressCallback )
367         cooker.buildDepgraph()
368         global parsed
369         parsed = True
370         print
371
372     def getvar( self, params ):
373         """Dump the contents of an outer BitBake environment variable"""
374         var = params[0]
375         value = data.getVar( var, make.cfg, 1 )
376         print value
377     getvar.usage = "<variable>"
378
379     def peek( self, params ):
380         """Dump contents of variable defined in providee's metadata"""
381         name, var = params
382         bbfile = self._findProvider( name )
383         if bbfile is not None:
384             value = make.pkgdata[bbfile].getVar( var, 1 )
385             print value
386         else:
387             print "ERROR: Nothing provides '%s'" % name
388     peek.usage = "<providee> <variable>"
389
390     #def poke( self, params ):
391     #    """Set contents of variable defined in providee's metadata"""
392     #    name, var, value = params
393     #    bbfile = self._findProvider( name )
394     #    if bbfile is not None:
395     #        make.pkgdata[bbfile].setVar( var, value )
396     #        print "OK"
397     #    else:
398     #        print "ERROR: Nothing provides '%s'" % name
399     #poke.usage = "<providee> <variable> <value>"
400
401     def print_( self, params ):
402         """Dump all files or providers"""
403         what = params[0]
404         if what == "files":
405             self._checkParsed()
406             for i in make.pkgdata.keys(): print i
407         elif what == "providers":
408             self._checkParsed()
409             for i in cooker.status.providers.keys(): print i
410         else:
411             print "Usage: print %s" % self.print_.usage
412     print_.usage = "<files|providers>"
413
414     def python( self, params ):
415         """Enter the expert mode - an interactive BitBake Python Interpreter"""
416         sys.ps1 = "EXPERT BB>>> "
417         sys.ps2 = "EXPERT BB... "
418         import code
419         interpreter = code.InteractiveConsole( dict( globals() ) )
420         interpreter.interact( "SHELL: Expert Mode - BitBake Python %s\nType 'help' for more information, press CTRL-D to switch back to BBSHELL." % sys.version )
421
422     def showdata( self, params ):
423         """Execute 'showdata' on a providee"""
424         self.build( params, "showdata" )
425     showdata.usage = "<providee>"
426
427     def setVar( self, params ):
428         """Set an outer BitBake environment variable"""
429         var, value = params
430         data.setVar( var, value, make.cfg )
431         print "OK"
432     setVar.usage = "<variable> <value>"
433
434     def rebuild( self, params ):
435         """Clean and rebuild a .bb file or a providee"""
436         self.build( params, "clean" )
437         self.build( params, "build" )
438     rebuild.usage = "<providee>"
439
440     def shell( self, params ):
441         """Execute a shell command and dump the output"""
442         if params != "":
443             print commands.getoutput( " ".join( params ) )
444     shell.usage = "<...>"
445
446     def stage( self, params ):
447         """Execute 'stage' on a providee"""
448         self.build( params, "stage" )
449     stage.usage = "<providee>"
450
451     def status( self, params ):
452         """<just for testing>"""
453         print "-" * 78
454         print "build cache = '%s'" % cooker.build_cache
455         print "build cache fail = '%s'" % cooker.build_cache_fail
456         print "building list = '%s'" % cooker.building_list
457         print "build path = '%s'" % cooker.build_path
458         print "consider_msgs_cache = '%s'" % cooker.consider_msgs_cache
459         print "build stats = '%s'" % cooker.stats
460         if last_exception is not None: print "last_exception = '%s'" % repr( last_exception.args )
461         print "memory output contents = '%s'" % self._shell.myout._buffer
462
463     def test( self, params ):
464         """<just for testing>"""
465         print "testCommand called with '%s'" % params
466
467     def unpack( self, params ):
468         """Execute 'unpack' on a providee"""
469         self.build( params, "unpack" )
470     unpack.usage = "<providee>"
471
472     def which( self, params ):
473         """Computes the providers for a given providee"""
474         item = params[0]
475
476         self._checkParsed()
477
478         preferred = data.getVar( "PREFERRED_PROVIDER_%s" % item, make.cfg, 1 )
479         if not preferred: preferred = item
480
481         try:
482             lv, lf, pv, pf = cooker.findBestProvider( preferred )
483         except KeyError:
484             lv, lf, pv, pf = (None,)*4
485
486         try:
487             providers = cooker.status.providers[item]
488         except KeyError:
489             print "SHELL: ERROR: Nothing provides", preferred
490         else:
491             for provider in providers:
492                 if provider == pf: provider = " (***) %s" % provider
493                 else:              provider = "       %s" % provider
494                 print provider
495     which.usage = "<providee>"
496
497 ##########################################################################
498 # Common helper functions
499 ##########################################################################
500
501 def completeFilePath( bbfile ):
502     """Get the complete bbfile path"""
503     if not make.pkgdata: return bbfile
504     for key in make.pkgdata.keys():
505         if key.endswith( bbfile ):
506             return key
507     return bbfile
508
509 def sendToPastebin( content ):
510     """Send content to http://www.pastebin.com"""
511     mydata = {}
512     mydata["parent_pid"] = ""
513     mydata["format"] = "bash"
514     mydata["code2"] = content
515     mydata["paste"] = "Send"
516     mydata["poster"] = "%s@%s" % ( os.environ.get( "USER", "unknown" ), socket.gethostname() or "unknown" )
517     params = urllib.urlencode( mydata )
518     headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain"}
519
520     conn = httplib.HTTPConnection( "pastebin.com:80" )
521     conn.request("POST", "/", params, headers )
522
523     response = conn.getresponse()
524     conn.close()
525
526     return response.status, response.reason, response.getheader( "location" ) or "unknown"
527
528 def completer( text, state ):
529     """Return a possible readline completion"""
530     debugOut( "completer called with text='%s', state='%d'" % ( text, state ) )
531
532     if state == 0:
533         line = readline.get_line_buffer()
534         if " " in line:
535             line = line.split()
536             # we are in second (or more) argument
537             if line[0] in cmds and hasattr( cmds[line[0]][0], "usage" ): # known command and usage
538                 u = getattr( cmds[line[0]][0], "usage" ).split()[0]
539                 if u == "<variable>":
540                     allmatches = make.cfg.keys()
541                 elif u == "<bbfile>":
542                     if make.pkgdata is None: allmatches = [ "(No Matches Available. Parsed yet?)" ]
543                     else: allmatches = [ x.split("/")[-1] for x in make.pkgdata.keys() ]
544                 elif u == "<providee>":
545                     if make.pkgdata is None: allmatches = [ "(No Matches Available. Parsed yet?)" ]
546                     else: allmatches = cooker.status.providers.iterkeys()
547                 else: allmatches = [ "(No tab completion available for this command)" ]
548             else: allmatches = [ "(No tab completion available for this command)" ]
549         else:
550             # we are in first argument
551             allmatches = cmds.iterkeys()
552
553         completer.matches = [ x for x in allmatches if x[:len(text)] == text ]
554         #print "completer.matches = '%s'" % completer.matches
555     if len( completer.matches ) > state:
556         return completer.matches[state]
557     else:
558         return None
559
560 def debugOut( text ):
561     if debug:
562         sys.stderr.write( "( %s )\n" % text )
563
564 ##########################################################################
565 # Class MemoryOutput
566 ##########################################################################
567
568 class MemoryOutput:
569     """File-like output class buffering the output of the last 10 commands"""
570     def __init__( self, delegate ):
571         self.delegate = delegate
572         self._buffer = []
573         self.text = []
574         self._command = None
575
576     def startCommand( self, command ):
577         self._command = command
578         self.text = []
579     def endCommand( self ):
580         if self._command is not None:
581             if len( self._buffer ) == 10: del self._buffer[0]
582             self._buffer.append( ( self._command, self.text ) )
583     def removeLast( self ):
584         if self._buffer:
585             del self._buffer[ len( self._buffer ) - 1 ]
586         self.text = []
587         self._command = None
588     def lastBuffer( self ):
589         if self._buffer:
590             return self._buffer[ len( self._buffer ) -1 ][1]
591     def bufferedCommands( self ):
592         return [ cmd for cmd, output in self._buffer ]
593     def buffer( self, i ):
594         if i < len( self._buffer ):
595             return "BB>> %s\n%s" % ( self._buffer[i][0], "".join( self._buffer[i][1] ) )
596         else: return "ERROR: Invalid buffer number. Buffer needs to be in (0, %d)" % ( len( self._buffer ) - 1 )
597     def write( self, text ):
598         if self._command is not None and text != "BB>> ": self.text.append( text )
599         if self.delegate is not None: self.delegate.write( text )
600     def flush( self ):
601         return self.delegate.flush()
602     def fileno( self ):
603         return self.delegate.fileno()
604     def isatty( self ):
605         return self.delegate.isatty()
606
607 ##########################################################################
608 # Class BitBakeShell
609 ##########################################################################
610
611 class BitBakeShell:
612
613     def __init__( self ):
614         """Register commands and set up readline"""
615         self.commands = BitBakeShellCommands( self )
616         self.myout = MemoryOutput( sys.stdout )
617         self.historyfilename = os.path.expanduser( "~/.bbsh_history" )
618         self.startupfilename = os.path.expanduser( "~/.bbsh_startup" )
619
620         readline.set_completer( completer )
621         readline.set_completer_delims( " " )
622         readline.parse_and_bind("tab: complete")
623
624         try:
625             readline.read_history_file( self.historyfilename )
626         except IOError:
627             pass  # It doesn't exist yet.
628
629         print __credits__
630
631     def cleanup( self ):
632         """Write readline history and clean up resources"""
633         debugOut( "writing command history" )
634         try:
635             readline.write_history_file( self.historyfilename )
636         except:
637             print "SHELL: Unable to save command history"
638
639     def registerCommand( self, command, function, numparams = 0, usage = "", helptext = "" ):
640         """Register a command"""
641         if usage == "": usage = command
642         if helptext == "": helptext = function.__doc__ or "<not yet documented>"
643         cmds[command] = ( function, numparams, usage, helptext )
644
645     def processCommand( self, command, params ):
646         """Process a command. Check number of params and print a usage string, if appropriate"""
647         debugOut( "processing command '%s'..." % command )
648         try:
649             function, numparams, usage, helptext = cmds[command]
650         except KeyError:
651             print "SHELL: ERROR: '%s' command is not a valid command." % command
652             self.myout.removeLast()
653         else:
654             if (numparams != -1) and (not len( params ) == numparams):
655                 print "Usage: '%s'" % usage
656                 return
657
658             result = function( self.commands, params )
659             debugOut( "result was '%s'" % result )
660
661     def processStartupFile( self ):
662         """Read and execute all commands found in $HOME/.bbsh_startup"""
663         if os.path.exists( self.startupfilename ):
664             startupfile = open( self.startupfilename, "r" )
665             # save_stdout = sys.stdout
666             # sys.stdout = open( "/dev/null", "w" )
667             numCommands = 0
668             for cmdline in startupfile.readlines():
669                 cmdline = cmdline.strip()
670                 debugOut( "processing startup line '%s'" % cmdline )
671                 if not cmdline:
672                     continue
673                 if "|" in cmdline:
674                     print >>save_stdout, "ERROR: ';' in startup file is not allowed. Ignoring line"
675                     continue
676                 allCommands = cmdline.split( ';' )
677                 for command in allCommands:
678                     if ' ' in command:
679                         self.processCommand( command.split()[0], command.split()[1:] )
680                     else:
681                         self.processCommand( command, "" )
682                     numCommands += 1
683             print "SHELL: Processed %d commands from '%s'" % ( numCommands, self.startupfilename )
684
685     def main( self ):
686         """The main command loop"""
687         while not leave_mainloop:
688             try:
689                 sys.stdout = self.myout.delegate
690                 cmdline = raw_input( "BB>> " )
691                 sys.stdout = self.myout
692                 if cmdline:
693                     allCommands = cmdline.split( ';' )
694                     for command in allCommands:
695                         pipecmd = None
696                         #
697                         # special case for expert mode
698                         if command == 'python':
699                             sys.stdout = self.myout.delegate
700                             self.processCommand( command, "" )
701                             sys.stdout = self.myout
702                         else:
703                             self.myout.startCommand( command )
704                             if '|' in command: # disable output
705                                 command, pipecmd = command.split( '|' )
706                                 delegate = self.myout.delegate
707                                 self.myout.delegate = None
708                             if ' ' in command:
709                                 self.processCommand( command.split()[0], command.split()[1:] )
710                             else:
711                                 self.processCommand( command, "" )
712                             self.myout.endCommand()
713                             if pipecmd is not None: # restore output
714                                 self.myout.delegate = delegate
715
716                                 pipe = popen2.Popen4( pipecmd )
717                                 pipe.tochild.write( "\n".join( self.myout.lastBuffer() ) )
718                                 pipe.tochild.close()
719                                 sys.stdout.write( pipe.fromchild.read() )
720                         #
721             except EOFError:
722                 print
723                 return
724             except KeyboardInterrupt:
725                 print
726
727 ##########################################################################
728 # Start function - called from the BitBake command line utility
729 ##########################################################################
730
731 def start( aCooker ):
732     global cooker
733     cooker = aCooker
734     bbshell = BitBakeShell()
735     bbshell.processStartupFile()
736     bbshell.main()
737     bbshell.cleanup()
738
739 if __name__ == "__main__":
740     print "SHELL: Sorry, this program should only be called by BitBake."