summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/shell.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/bb/shell.py')
-rw-r--r--bitbake/lib/bb/shell.py779
1 files changed, 779 insertions, 0 deletions
diff --git a/bitbake/lib/bb/shell.py b/bitbake/lib/bb/shell.py
new file mode 100644
index 0000000000..97e61e1169
--- /dev/null
+++ b/bitbake/lib/bb/shell.py
@@ -0,0 +1,779 @@
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"""
23BitBake Shell
24
25IDEAS:
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
38PROBLEMS:
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
48try:
49 set
50except NameError:
51 from sets import Set as set
52import sys, os, imp, readline, socket, httplib, urllib, commands, popen2, copy, shlex, Queue, fnmatch
53imp.load_source( "bitbake", os.path.dirname( sys.argv[0] )+"/bitbake" )
54from 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>
58Type 'help' for more information, press CTRL-D to exit.""" % __version__
59
60cmds = {}
61leave_mainloop = False
62last_exception = None
63cooker = None
64parsed = False
65initdata = None
66debug = os.environ.get( "BBSHELL_DEBUG", "" )
67
68##########################################################################
69# Class BitBakeShellCommands
70##########################################################################
71
72class 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 = ""
315SECTION = ""
316AUTHOR = ""
317HOMEPAGE = ""
318MAINTAINER = ""
319LICENSE = "GPL"
320PR = "r0"
321
322SRC_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
527def 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
535def 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
554def 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
586def debugOut( text ):
587 if debug:
588 sys.stderr.write( "( %s )\n" % text )
589
590def 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
605def globfilter( names, pattern ):
606 return fnmatch.filter( names, pattern )
607
608##########################################################################
609# Class MemoryOutput
610##########################################################################
611
612class 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
655class 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
770def start( aCooker ):
771 global cooker
772 cooker = aCooker
773 bbshell = BitBakeShell()
774 bbshell.processStartupFile()
775 bbshell.main()
776 bbshell.cleanup()
777
778if __name__ == "__main__":
779 print "SHELL: Sorry, this program should only be called by BitBake."