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