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