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