Shell:
[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         - output control, i.e. pipe output into grep, sort, etc.
34         - job control, i.e. bring running commands into background and foreground
35         - wildcards for commands, i.e. build *opie*
36     * start parsing in background right after startup
37     * ncurses interface
38
39 PROBLEMS:
40     * force doesn't always work
41     * readline completion for commands with more than one parameters
42
43 """
44
45 ##########################################################################
46 # Import and setup global variables
47 ##########################################################################
48
49 try:
50     set
51 except NameError:
52     from sets import Set as set
53 import sys, os, imp, readline, socket, httplib, urllib, commands, popen2, copy, shlex, Queue
54 imp.load_source( "bitbake", os.path.dirname( sys.argv[0] )+"/bitbake" )
55 from bb import data, parse, build, fatal
56
57 __version__ = "0.5.2"
58 __credits__ = """BitBake Shell Version %s (C) 2005 Michael 'Mickey' Lauer <mickey@Vanille.de>
59 Type 'help' for more information, press CTRL-D to exit.""" % __version__
60
61 cmds = {}
62 leave_mainloop = False
63 last_exception = None
64 cooker = None
65 parsed = False
66 initdata = None
67 debug = os.environ.get( "BBSHELL_DEBUG", "" )
68
69 ##########################################################################
70 # Class BitBakeShellCommands
71 ##########################################################################
72
73 class BitBakeShellCommands:
74     """This class contains the valid commands for the shell"""
75
76     def __init__( self, shell ):
77         """Register all the commands"""
78         self._shell = shell
79         for attr in BitBakeShellCommands.__dict__:
80             if not attr.startswith( "_" ):
81                 if attr.endswith( "_" ):
82                     command = attr[:-1].lower()
83                 else:
84                     command = attr[:].lower()
85                 method = getattr( BitBakeShellCommands, attr )
86                 debugOut( "registering command '%s'" % command )
87                 # scan number of arguments
88                 usage = getattr( method, "usage", "" )
89                 if usage != "<...>":
90                     numArgs = len( usage.split() )
91                 else:
92                     numArgs = -1
93                 shell.registerCommand( command, method, numArgs, "%s %s" % ( command, usage ), method.__doc__ )
94
95     def _checkParsed( self ):
96         if not parsed:
97             print "SHELL: This command needs to parse bbfiles..."
98             self.parse( None )
99
100     def _findProvider( self, item ):
101         self._checkParsed()
102         preferred = data.getVar( "PREFERRED_PROVIDER_%s" % item, cooker.configuration.data, 1 )
103         if not preferred: preferred = item
104         try:
105             lv, lf, pv, pf = cooker.findBestProvider( preferred )
106         except KeyError:
107             if item in cooker.status.providers:
108                 pf = cooker.status.providers[item][0]
109             else:
110                 pf = None
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 = cooker.configuration.cmd
145         cooker.configuration.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         cooker.configuration.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__, cooker.configuration.data, 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 = cooker.configuration.cmd
208         cooker.configuration.cmd = cmd
209         cooker.build_cache = []
210         cooker.build_cache_fail = []
211
212         thisdata = copy.deepcopy( initdata )
213         # Caution: parse.handle modifies thisdata, hence it would
214         # lead to pollution cooker.configuration.data, which is
215         # why we use it on a safe copy we obtained from cooker right after
216         # parsing the initial *.conf files
217         try:
218             bbfile_data = parse.handle( bf, thisdata )
219         except parse.ParseError:
220             print "ERROR: Unable to open or parse '%s'" % bf
221         else:
222             item = data.getVar('PN', bbfile_data, 1)
223             data.setVar( "_task_cache", [], bbfile_data ) # force
224             try:
225                 cooker.tryBuildPackage( os.path.abspath( bf ), item, bbfile_data )
226             except build.EventException, e:
227                 print "ERROR: Couldn't build '%s'" % name
228                 global last_exception
229                 last_exception = e
230
231         cooker.configuration.cmd = oldcmd
232     fileBuild.usage = "<bbfile>"
233
234     def fileClean( self, params ):
235         """Clean a .bb file"""
236         self.fileBuild( params, "clean" )
237     fileClean.usage = "<bbfile>"
238
239     def fileEdit( self, params ):
240         """Call $EDITOR on a .bb file"""
241         name = params[0]
242         os.system( "%s %s" % ( os.environ.get( "EDITOR", "vi" ), completeFilePath( name ) ) )
243     fileEdit.usage = "<bbfile>"
244
245     def fileRebuild( self, params ):
246         """Rebuild (clean & build) a .bb file"""
247         self.fileClean( params )
248         self.fileBuild( params )
249     fileRebuild.usage = "<bbfile>"
250
251     def force( self, params ):
252         """Toggle force task execution flag (see bitbake -f)"""
253         cooker.configuration.force = not cooker.configuration.force
254         print "SHELL: Force Flag is now '%s'" % repr( cooker.configuration.force )
255
256     def help( self, params ):
257         """Show a comprehensive list of commands and their purpose"""
258         print "="*30, "Available Commands", "="*30
259         allcmds = cmds.keys()
260         allcmds.sort()
261         for cmd in allcmds:
262             function,numparams,usage,helptext = cmds[cmd]
263             print "| %s | %s" % (usage.ljust(30), helptext)
264         print "="*78
265
266     def lastError( self, params ):
267         """Show the reason or log that was produced by the last BitBake event exception"""
268         if last_exception is None:
269             print "SHELL: No Errors yet (Phew)..."
270         else:
271             reason, event = last_exception.args
272             print "SHELL: Reason for the last error: '%s'" % reason
273             if ':' in reason:
274                 msg, filename = reason.split( ':' )
275                 filename = filename.strip()
276                 print "SHELL: Dumping log file for last error:"
277                 try:
278                     print open( filename ).read()
279                 except IOError:
280                     print "ERROR: Couldn't open '%s'" % filename
281
282     def new( self, params ):
283         """Create a new .bb file and open the editor"""
284         dirname, filename = params
285         packages = '/'.join( data.getVar( "BBFILES", cooker.configuration.data, 1 ).split('/')[:-2] )
286         fulldirname = "%s/%s" % ( packages, dirname )
287
288         if not os.path.exists( fulldirname ):
289             print "SHELL: Creating '%s'" % fulldirname
290             os.mkdir( fulldirname )
291         if os.path.exists( fulldirname ) and os.path.isdir( fulldirname ):
292             if os.path.exists( "%s/%s" % ( fulldirname, filename ) ):
293                 print "SHELL: ERROR: %s/%s already exists" % ( fulldirname, filename )
294                 return False
295             print "SHELL: Creating '%s/%s'" % ( fulldirname, filename )
296             newpackage = open( "%s/%s" % ( fulldirname, filename ), "w" )
297             print >>newpackage,"""DESCRIPTION = ""
298 SECTION = ""
299 AUTHOR = ""
300 HOMEPAGE = ""
301 MAINTAINER = ""
302 LICENSE = "GPL"
303 PR = "r0"
304
305 SRC_URI = ""
306
307 #inherit base
308
309 #do_configure() {
310 #
311 #}
312
313 #do_compile() {
314 #
315 #}
316
317 #do_stage() {
318 #
319 #}
320
321 #do_install() {
322 #
323 #}
324 """
325             newpackage.close()
326             os.system( "%s %s/%s" % ( os.environ.get( "EDITOR" ), fulldirname, filename ) )
327     new.usage = "<directory> <filename>"
328
329     def pasteBin( self, params ):
330         """Send a command + output buffer to http://pastebin.com"""
331         index = params[0]
332         contents = self._shell.myout.buffer( int( index ) )
333         status, error, location = sendToPastebin( contents )
334         if status == 302:
335             print "SHELL: Pasted to %s" % location
336         else:
337             print "ERROR: %s %s" % ( status, error )
338     pasteBin.usage = "<index>"
339
340     def pasteLog( self, params ):
341         """Send the last event exception error log (if there is one) to http://pastebin.com"""
342         if last_exception is None:
343             print "SHELL: No Errors yet (Phew)..."
344         else:
345             reason, event = last_exception.args
346             print "SHELL: Reason for the last error: '%s'" % reason
347             if ':' in reason:
348                 msg, filename = reason.split( ':' )
349                 filename = filename.strip()
350                 print "SHELL: Pasting log file to pastebin..."
351
352                 status, error, location = sendToPastebin( open( filename ).read() )
353
354                 if status == 302:
355                     print "SHELL: Pasted to %s" % location
356                 else:
357                     print "ERROR: %s %s" % ( status, error )
358
359     def patch( self, params ):
360         """Execute 'patch' command on a providee"""
361         self.build( params, "patch" )
362     patch.usage = "<providee>"
363
364     def parse( self, params ):
365         """(Re-)parse .bb files and calculate the dependency graph"""
366         cooker.status = cooker.ParsingStatus()
367         ignore = data.getVar("ASSUME_PROVIDED", cooker.configuration.data, 1) or ""
368         cooker.status.ignored_dependencies = set( ignore.split() )
369         cooker.handleCollections( data.getVar("BBFILE_COLLECTIONS", cooker.configuration.data, 1) )
370
371         cooker.collect_bbfiles( cooker.myProgressCallback )
372         cooker.buildDepgraph()
373         global parsed
374         parsed = True
375         print
376
377     def getvar( self, params ):
378         """Dump the contents of an outer BitBake environment variable"""
379         var = params[0]
380         value = data.getVar( var, cooker.configuration.data, 1 )
381         print value
382     getvar.usage = "<variable>"
383
384     def peek( self, params ):
385         """Dump contents of variable defined in providee's metadata"""
386         name, var = params
387         bbfile = self._findProvider( name )
388         if bbfile is not None:
389             value = cooker.pkgdata[bbfile].getVar( var, 1 )
390             print value
391         else:
392             print "ERROR: Nothing provides '%s'" % name
393     peek.usage = "<providee> <variable>"
394
395     def poke( self, params ):
396         """Set contents of variable defined in providee's metadata"""
397         name, var, value = params
398         bbfile = self._findProvider( name )
399         d = cooker.pkgdata[bbfile]
400         if bbfile is not None:
401             data.setVar( var, value, d )
402
403             # mark the change semi persistant
404             cooker.pkgdata.setDirty(bbfile, d)
405             print "OK"
406         else:
407             print "ERROR: Nothing provides '%s'" % name
408     poke.usage = "<providee> <variable> <value>"
409
410     def print_( self, params ):
411         """Dump all files or providers"""
412         what = params[0]
413         if what == "files":
414             self._checkParsed()
415             for key in cooker.pkgdata.keys(): print key
416         elif what == "providers":
417             self._checkParsed()
418             for key in cooker.status.providers.keys(): print key
419         else:
420             print "Usage: print %s" % self.print_.usage
421     print_.usage = "<files|providers>"
422
423     def python( self, params ):
424         """Enter the expert mode - an interactive BitBake Python Interpreter"""
425         sys.ps1 = "EXPERT BB>>> "
426         sys.ps2 = "EXPERT BB... "
427         import code
428         interpreter = code.InteractiveConsole( dict( globals() ) )
429         interpreter.interact( "SHELL: Expert Mode - BitBake Python %s\nType 'help' for more information, press CTRL-D to switch back to BBSHELL." % sys.version )
430
431     def showdata( self, params ):
432         """Execute 'showdata' on a providee"""
433         self.build( params, "showdata" )
434     showdata.usage = "<providee>"
435
436     def setVar( self, params ):
437         """Set an outer BitBake environment variable"""
438         var, value = params
439         data.setVar( var, value, cooker.configuration.data )
440         print "OK"
441     setVar.usage = "<variable> <value>"
442
443     def rebuild( self, params ):
444         """Clean and rebuild a .bb file or a providee"""
445         self.build( params, "clean" )
446         self.build( params, "build" )
447     rebuild.usage = "<providee>"
448
449     def shell( self, params ):
450         """Execute a shell command and dump the output"""
451         if params != "":
452             print commands.getoutput( " ".join( params ) )
453     shell.usage = "<...>"
454
455     def stage( self, params ):
456         """Execute 'stage' on a providee"""
457         self.build( params, "stage" )
458     stage.usage = "<providee>"
459
460     def status( self, params ):
461         """<just for testing>"""
462         print "-" * 78
463         print "build cache = '%s'" % cooker.build_cache
464         print "build cache fail = '%s'" % cooker.build_cache_fail
465         print "building list = '%s'" % cooker.building_list
466         print "build path = '%s'" % cooker.build_path
467         print "consider_msgs_cache = '%s'" % cooker.consider_msgs_cache
468         print "build stats = '%s'" % cooker.stats
469         if last_exception is not None: print "last_exception = '%s'" % repr( last_exception.args )
470         print "memory output contents = '%s'" % self._shell.myout._buffer
471
472     def test( self, params ):
473         """<just for testing>"""
474         print "testCommand called with '%s'" % params
475
476     def unpack( self, params ):
477         """Execute 'unpack' on a providee"""
478         self.build( params, "unpack" )
479     unpack.usage = "<providee>"
480
481     def which( self, params ):
482         """Computes the providers for a given providee"""
483         item = params[0]
484
485         self._checkParsed()
486
487         preferred = data.getVar( "PREFERRED_PROVIDER_%s" % item, cooker.configuration.data, 1 )
488         if not preferred: preferred = item
489
490         try:
491             lv, lf, pv, pf = cooker.findBestProvider( preferred )
492         except KeyError:
493             lv, lf, pv, pf = (None,)*4
494
495         try:
496             providers = cooker.status.providers[item]
497         except KeyError:
498             print "SHELL: ERROR: Nothing provides", preferred
499         else:
500             for provider in providers:
501                 if provider == pf: provider = " (***) %s" % provider
502                 else:              provider = "       %s" % provider
503                 print provider
504     which.usage = "<providee>"
505
506 ##########################################################################
507 # Common helper functions
508 ##########################################################################
509
510 def completeFilePath( bbfile ):
511     """Get the complete bbfile path"""
512     if not cooker.pkgdata: return bbfile
513     for key in cooker.pkgdata.keys():
514         if key.endswith( bbfile ):
515             return key
516     return bbfile
517
518 def sendToPastebin( content ):
519     """Send content to http://www.pastebin.com"""
520     mydata = {}
521     mydata["parent_pid"] = ""
522     mydata["format"] = "bash"
523     mydata["code2"] = content
524     mydata["paste"] = "Send"
525     mydata["poster"] = "%s@%s" % ( os.environ.get( "USER", "unknown" ), socket.gethostname() or "unknown" )
526     params = urllib.urlencode( mydata )
527     headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain"}
528
529     conn = httplib.HTTPConnection( "pastebin.com:80" )
530     conn.request("POST", "/", params, headers )
531
532     response = conn.getresponse()
533     conn.close()
534
535     return response.status, response.reason, response.getheader( "location" ) or "unknown"
536
537 def completer( text, state ):
538     """Return a possible readline completion"""
539     debugOut( "completer called with text='%s', state='%d'" % ( text, state ) )
540
541     if state == 0:
542         line = readline.get_line_buffer()
543         if " " in line:
544             line = line.split()
545             # we are in second (or more) argument
546             if line[0] in cmds and hasattr( cmds[line[0]][0], "usage" ): # known command and usage
547                 u = getattr( cmds[line[0]][0], "usage" ).split()[0]
548                 if u == "<variable>":
549                     allmatches = cooker.configuration.data.keys()
550                 elif u == "<bbfile>":
551                     if cooker.pkgdata is None: allmatches = [ "(No Matches Available. Parsed yet?)" ]
552                     else: allmatches = [ x.split("/")[-1] for x in cooker.pkgdata.keys() ]
553                 elif u == "<providee>":
554                     if cooker.pkgdata is None: allmatches = [ "(No Matches Available. Parsed yet?)" ]
555                     else: allmatches = cooker.status.providers.iterkeys()
556                 else: allmatches = [ "(No tab completion available for this command)" ]
557             else: allmatches = [ "(No tab completion available for this command)" ]
558         else:
559             # we are in first argument
560             allmatches = cmds.iterkeys()
561
562         completer.matches = [ x for x in allmatches if x[:len(text)] == text ]
563         #print "completer.matches = '%s'" % completer.matches
564     if len( completer.matches ) > state:
565         return completer.matches[state]
566     else:
567         return None
568
569 def debugOut( text ):
570     if debug:
571         sys.stderr.write( "( %s )\n" % text )
572
573 def columnize( alist, width = 80 ):
574     """
575     A word-wrap function that preserves existing line breaks
576     and most spaces in the text. Expects that existing line
577     breaks are posix newlines (\n).
578     """
579     return reduce(lambda line, word, width=width: '%s%s%s' %
580                   (line,
581                    ' \n'[(len(line[line.rfind('\n')+1:])
582                          + len(word.split('\n',1)[0]
583                               ) >= width)],
584                    word),
585                   alist
586                  )
587
588 ##########################################################################
589 # Class MemoryOutput
590 ##########################################################################
591
592 class MemoryOutput:
593     """File-like output class buffering the output of the last 10 commands"""
594     def __init__( self, delegate ):
595         self.delegate = delegate
596         self._buffer = []
597         self.text = []
598         self._command = None
599
600     def startCommand( self, command ):
601         self._command = command
602         self.text = []
603     def endCommand( self ):
604         if self._command is not None:
605             if len( self._buffer ) == 10: del self._buffer[0]
606             self._buffer.append( ( self._command, self.text ) )
607     def removeLast( self ):
608         if self._buffer:
609             del self._buffer[ len( self._buffer ) - 1 ]
610         self.text = []
611         self._command = None
612     def lastBuffer( self ):
613         if self._buffer:
614             return self._buffer[ len( self._buffer ) -1 ][1]
615     def bufferedCommands( self ):
616         return [ cmd for cmd, output in self._buffer ]
617     def buffer( self, i ):
618         if i < len( self._buffer ):
619             return "BB>> %s\n%s" % ( self._buffer[i][0], "".join( self._buffer[i][1] ) )
620         else: return "ERROR: Invalid buffer number. Buffer needs to be in (0, %d)" % ( len( self._buffer ) - 1 )
621     def write( self, text ):
622         if self._command is not None and text != "BB>> ": self.text.append( text )
623         if self.delegate is not None: self.delegate.write( text )
624     def flush( self ):
625         return self.delegate.flush()
626     def fileno( self ):
627         return self.delegate.fileno()
628     def isatty( self ):
629         return self.delegate.isatty()
630
631 ##########################################################################
632 # Class BitBakeShell
633 ##########################################################################
634
635 class BitBakeShell:
636
637     def __init__( self ):
638         """Register commands and set up readline"""
639         self.commandQ = Queue.Queue()
640         self.commands = BitBakeShellCommands( self )
641         self.myout = MemoryOutput( sys.stdout )
642         self.historyfilename = os.path.expanduser( "~/.bbsh_history" )
643         self.startupfilename = os.path.expanduser( "~/.bbsh_startup" )
644
645         readline.set_completer( completer )
646         readline.set_completer_delims( " " )
647         readline.parse_and_bind("tab: complete")
648
649         try:
650             readline.read_history_file( self.historyfilename )
651         except IOError:
652             pass  # It doesn't exist yet.
653
654         print __credits__
655
656         # save initial cooker configuration (will be reused in file*** commands)
657         global initdata
658         initdata = copy.deepcopy( cooker.configuration.data )
659
660     def cleanup( self ):
661         """Write readline history and clean up resources"""
662         debugOut( "writing command history" )
663         try:
664             readline.write_history_file( self.historyfilename )
665         except:
666             print "SHELL: Unable to save command history"
667
668     def registerCommand( self, command, function, numparams = 0, usage = "", helptext = "" ):
669         """Register a command"""
670         if usage == "": usage = command
671         if helptext == "": helptext = function.__doc__ or "<not yet documented>"
672         cmds[command] = ( function, numparams, usage, helptext )
673
674     def processCommand( self, command, params ):
675         """Process a command. Check number of params and print a usage string, if appropriate"""
676         debugOut( "processing command '%s'..." % command )
677         try:
678             function, numparams, usage, helptext = cmds[command]
679         except KeyError:
680             print "SHELL: ERROR: '%s' command is not a valid command." % command
681             self.myout.removeLast()
682         else:
683             if (numparams != -1) and (not len( params ) == numparams):
684                 print "Usage: '%s'" % usage
685                 return
686
687             result = function( self.commands, params )
688             debugOut( "result was '%s'" % result )
689
690     def processStartupFile( self ):
691         """Read and execute all commands found in $HOME/.bbsh_startup"""
692         if os.path.exists( self.startupfilename ):
693             startupfile = open( self.startupfilename, "r" )
694             for cmdline in startupfile:
695                 debugOut( "processing startup line '%s'" % cmdline )
696                 if not cmdline:
697                     continue
698                 if "|" in cmdline:
699                     print "ERROR: '|' in startup file is not allowed. Ignoring line"
700                     continue
701                 self.commandQ.put( cmdline.strip() )
702
703     def main( self ):
704         """The main command loop"""
705         while not leave_mainloop:
706             try:
707                 if self.commandQ.empty():
708                     sys.stdout = self.myout.delegate
709                     cmdline = raw_input( "BB>> " )
710                     sys.stdout = self.myout
711                 else:
712                     cmdline = self.commandQ.get()
713                 if cmdline:
714                     allCommands = cmdline.split( ';' )
715                     for command in allCommands:
716                         pipecmd = None
717                         #
718                         # special case for expert mode
719                         if command == 'python':
720                             sys.stdout = self.myout.delegate
721                             self.processCommand( command, "" )
722                             sys.stdout = self.myout
723                         else:
724                             self.myout.startCommand( command )
725                             if '|' in command: # disable output
726                                 command, pipecmd = command.split( '|' )
727                                 delegate = self.myout.delegate
728                                 self.myout.delegate = None
729                             tokens = shlex.split( command, True )
730                             self.processCommand( tokens[0], tokens[1:] or "" )
731                             self.myout.endCommand()
732                             if pipecmd is not None: # restore output
733                                 self.myout.delegate = delegate
734
735                                 pipe = popen2.Popen4( pipecmd )
736                                 pipe.tochild.write( "\n".join( self.myout.lastBuffer() ) )
737                                 pipe.tochild.close()
738                                 sys.stdout.write( pipe.fromchild.read() )
739                         #
740             except EOFError:
741                 print
742                 return
743             except KeyboardInterrupt:
744                 print
745
746 ##########################################################################
747 # Start function - called from the BitBake command line utility
748 ##########################################################################
749
750 def start( aCooker ):
751     global cooker
752     cooker = aCooker
753     bbshell = BitBakeShell()
754     bbshell.processStartupFile()
755     bbshell.main()
756     bbshell.cleanup()
757
758 if __name__ == "__main__":
759     print "SHELL: Sorry, this program should only be called by BitBake."