summaryrefslogtreecommitdiffstats
path: root/bitbake-dev/lib/bb/shell.py
diff options
context:
space:
mode:
authorRichard Purdie <richard@openedhand.com>2008-09-30 15:08:33 +0000
committerRichard Purdie <richard@openedhand.com>2008-09-30 15:08:33 +0000
commitc30eddb243e7e65f67f656e62848a033cf6f2e5c (patch)
tree110dd95788b76f55d31cb8d30aac2de8400b6f4a /bitbake-dev/lib/bb/shell.py
parent5ef0510474004eeb2ae8a99b64e2febb1920e077 (diff)
downloadpoky-c30eddb243e7e65f67f656e62848a033cf6f2e5c.tar.gz
Add bitbake-dev to allow ease of testing and development of bitbake trunk
git-svn-id: https://svn.o-hand.com/repos/poky/trunk@5337 311d38ba-8fff-0310-9ca6-ca027cbcb966
Diffstat (limited to 'bitbake-dev/lib/bb/shell.py')
-rw-r--r--bitbake-dev/lib/bb/shell.py827
1 files changed, 827 insertions, 0 deletions
diff --git a/bitbake-dev/lib/bb/shell.py b/bitbake-dev/lib/bb/shell.py
new file mode 100644
index 0000000000..34828fe425
--- /dev/null
+++ b/bitbake-dev/lib/bb/shell.py
@@ -0,0 +1,827 @@
1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3##########################################################################
4#
5# Copyright (C) 2005-2006 Michael 'Mickey' Lauer <mickey@Vanille.de>
6# Copyright (C) 2005-2006 Vanille Media
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20#
21##########################################################################
22#
23# Thanks to:
24# * Holger Freyther <zecke@handhelds.org>
25# * Justin Patrin <papercrane@reversefold.com>
26#
27##########################################################################
28
29"""
30BitBake Shell
31
32IDEAS:
33 * list defined tasks per package
34 * list classes
35 * toggle force
36 * command to reparse just one (or more) bbfile(s)
37 * automatic check if reparsing is necessary (inotify?)
38 * frontend for bb file manipulation
39 * more shell-like features:
40 - output control, i.e. pipe output into grep, sort, etc.
41 - job control, i.e. bring running commands into background and foreground
42 * start parsing in background right after startup
43 * ncurses interface
44
45PROBLEMS:
46 * force doesn't always work
47 * readline completion for commands with more than one parameters
48
49"""
50
51##########################################################################
52# Import and setup global variables
53##########################################################################
54
55try:
56 set
57except NameError:
58 from sets import Set as set
59import sys, os, readline, socket, httplib, urllib, commands, popen2, copy, shlex, Queue, fnmatch
60from bb import data, parse, build, fatal, cache, taskdata, runqueue, providers as Providers
61
62__version__ = "0.5.3.1"
63__credits__ = """BitBake Shell Version %s (C) 2005 Michael 'Mickey' Lauer <mickey@Vanille.de>
64Type 'help' for more information, press CTRL-D to exit.""" % __version__
65
66cmds = {}
67leave_mainloop = False
68last_exception = None
69cooker = None
70parsed = False
71debug = os.environ.get( "BBSHELL_DEBUG", "" )
72
73##########################################################################
74# Class BitBakeShellCommands
75##########################################################################
76
77class BitBakeShellCommands:
78 """This class contains the valid commands for the shell"""
79
80 def __init__( self, shell ):
81 """Register all the commands"""
82 self._shell = shell
83 for attr in BitBakeShellCommands.__dict__:
84 if not attr.startswith( "_" ):
85 if attr.endswith( "_" ):
86 command = attr[:-1].lower()
87 else:
88 command = attr[:].lower()
89 method = getattr( BitBakeShellCommands, attr )
90 debugOut( "registering command '%s'" % command )
91 # scan number of arguments
92 usage = getattr( method, "usage", "" )
93 if usage != "<...>":
94 numArgs = len( usage.split() )
95 else:
96 numArgs = -1
97 shell.registerCommand( command, method, numArgs, "%s %s" % ( command, usage ), method.__doc__ )
98
99 def _checkParsed( self ):
100 if not parsed:
101 print "SHELL: This command needs to parse bbfiles..."
102 self.parse( None )
103
104 def _findProvider( self, item ):
105 self._checkParsed()
106 # Need to use taskData for this information
107 preferred = data.getVar( "PREFERRED_PROVIDER_%s" % item, cooker.configuration.data, 1 )
108 if not preferred: preferred = item
109 try:
110 lv, lf, pv, pf = Providers.findBestProvider(preferred, cooker.configuration.data, cooker.status)
111 except KeyError:
112 if item in cooker.status.providers:
113 pf = cooker.status.providers[item][0]
114 else:
115 pf = None
116 return pf
117
118 def alias( self, params ):
119 """Register a new name for a command"""
120 new, old = params
121 if not old in cmds:
122 print "ERROR: Command '%s' not known" % old
123 else:
124 cmds[new] = cmds[old]
125 print "OK"
126 alias.usage = "<alias> <command>"
127
128 def buffer( self, params ):
129 """Dump specified output buffer"""
130 index = params[0]
131 print self._shell.myout.buffer( int( index ) )
132 buffer.usage = "<index>"
133
134 def buffers( self, params ):
135 """Show the available output buffers"""
136 commands = self._shell.myout.bufferedCommands()
137 if not commands:
138 print "SHELL: No buffered commands available yet. Start doing something."
139 else:
140 print "="*35, "Available Output Buffers", "="*27
141 for index, cmd in enumerate( commands ):
142 print "| %s %s" % ( str( index ).ljust( 3 ), cmd )
143 print "="*88
144
145 def build( self, params, cmd = "build" ):
146 """Build a providee"""
147 global last_exception
148 globexpr = params[0]
149 self._checkParsed()
150 names = globfilter( cooker.status.pkg_pn.keys(), globexpr )
151 if len( names ) == 0: names = [ globexpr ]
152 print "SHELL: Building %s" % ' '.join( names )
153
154 oldcmd = cooker.configuration.cmd
155 cooker.configuration.cmd = cmd
156
157 td = taskdata.TaskData(cooker.configuration.abort)
158 localdata = data.createCopy(cooker.configuration.data)
159 data.update_data(localdata)
160 data.expandKeys(localdata)
161
162 try:
163 tasks = []
164 for name in names:
165 td.add_provider(localdata, cooker.status, name)
166 providers = td.get_provider(name)
167
168 if len(providers) == 0:
169 raise Providers.NoProvider
170
171 tasks.append([name, "do_%s" % cooker.configuration.cmd])
172
173 td.add_unresolved(localdata, cooker.status)
174
175 rq = runqueue.RunQueue(cooker, localdata, cooker.status, td, tasks)
176 rq.prepare_runqueue()
177 rq.execute_runqueue()
178
179 except Providers.NoProvider:
180 print "ERROR: No Provider"
181 last_exception = Providers.NoProvider
182
183 except runqueue.TaskFailure, fnids:
184 for fnid in fnids:
185 print "ERROR: '%s' failed" % td.fn_index[fnid]
186 last_exception = runqueue.TaskFailure
187
188 except build.EventException, e:
189 print "ERROR: Couldn't build '%s'" % names
190 last_exception = e
191
192 cooker.configuration.cmd = oldcmd
193
194 build.usage = "<providee>"
195
196 def clean( self, params ):
197 """Clean a providee"""
198 self.build( params, "clean" )
199 clean.usage = "<providee>"
200
201 def compile( self, params ):
202 """Execute 'compile' on a providee"""
203 self.build( params, "compile" )
204 compile.usage = "<providee>"
205
206 def configure( self, params ):
207 """Execute 'configure' on a providee"""
208 self.build( params, "configure" )
209 configure.usage = "<providee>"
210
211 def edit( self, params ):
212 """Call $EDITOR on a providee"""
213 name = params[0]
214 bbfile = self._findProvider( name )
215 if bbfile is not None:
216 os.system( "%s %s" % ( os.environ.get( "EDITOR", "vi" ), bbfile ) )
217 else:
218 print "ERROR: Nothing provides '%s'" % name
219 edit.usage = "<providee>"
220
221 def environment( self, params ):
222 """Dump out the outer BitBake environment"""
223 cooker.showEnvironment()
224
225 def exit_( self, params ):
226 """Leave the BitBake Shell"""
227 debugOut( "setting leave_mainloop to true" )
228 global leave_mainloop
229 leave_mainloop = True
230
231 def fetch( self, params ):
232 """Fetch a providee"""
233 self.build( params, "fetch" )
234 fetch.usage = "<providee>"
235
236 def fileBuild( self, params, cmd = "build" ):
237 """Parse and build a .bb file"""
238 global last_exception
239 name = params[0]
240 bf = completeFilePath( name )
241 print "SHELL: Calling '%s' on '%s'" % ( cmd, bf )
242
243 oldcmd = cooker.configuration.cmd
244 cooker.configuration.cmd = cmd
245
246 try:
247 cooker.buildFile(bf)
248 except parse.ParseError:
249 print "ERROR: Unable to open or parse '%s'" % bf
250 except build.EventException, e:
251 print "ERROR: Couldn't build '%s'" % name
252 last_exception = e
253
254 cooker.configuration.cmd = oldcmd
255 fileBuild.usage = "<bbfile>"
256
257 def fileClean( self, params ):
258 """Clean a .bb file"""
259 self.fileBuild( params, "clean" )
260 fileClean.usage = "<bbfile>"
261
262 def fileEdit( self, params ):
263 """Call $EDITOR on a .bb file"""
264 name = params[0]
265 os.system( "%s %s" % ( os.environ.get( "EDITOR", "vi" ), completeFilePath( name ) ) )
266 fileEdit.usage = "<bbfile>"
267
268 def fileRebuild( self, params ):
269 """Rebuild (clean & build) a .bb file"""
270 self.fileBuild( params, "rebuild" )
271 fileRebuild.usage = "<bbfile>"
272
273 def fileReparse( self, params ):
274 """(re)Parse a bb file"""
275 bbfile = params[0]
276 print "SHELL: Parsing '%s'" % bbfile
277 parse.update_mtime( bbfile )
278 cooker.bb_cache.cacheValidUpdate(bbfile)
279 fromCache = cooker.bb_cache.loadData(bbfile, cooker.configuration.data)
280 cooker.bb_cache.sync()
281 if False: #fromCache:
282 print "SHELL: File has not been updated, not reparsing"
283 else:
284 print "SHELL: Parsed"
285 fileReparse.usage = "<bbfile>"
286
287 def abort( self, params ):
288 """Toggle abort task execution flag (see bitbake -k)"""
289 cooker.configuration.abort = not cooker.configuration.abort
290 print "SHELL: Abort Flag is now '%s'" % repr( cooker.configuration.abort )
291
292 def force( self, params ):
293 """Toggle force task execution flag (see bitbake -f)"""
294 cooker.configuration.force = not cooker.configuration.force
295 print "SHELL: Force Flag is now '%s'" % repr( cooker.configuration.force )
296
297 def help( self, params ):
298 """Show a comprehensive list of commands and their purpose"""
299 print "="*30, "Available Commands", "="*30
300 allcmds = cmds.keys()
301 allcmds.sort()
302 for cmd in allcmds:
303 function,numparams,usage,helptext = cmds[cmd]
304 print "| %s | %s" % (usage.ljust(30), helptext)
305 print "="*78
306
307 def lastError( self, params ):
308 """Show the reason or log that was produced by the last BitBake event exception"""
309 if last_exception is None:
310 print "SHELL: No Errors yet (Phew)..."
311 else:
312 reason, event = last_exception.args
313 print "SHELL: Reason for the last error: '%s'" % reason
314 if ':' in reason:
315 msg, filename = reason.split( ':' )
316 filename = filename.strip()
317 print "SHELL: Dumping log file for last error:"
318 try:
319 print open( filename ).read()
320 except IOError:
321 print "ERROR: Couldn't open '%s'" % filename
322
323 def match( self, params ):
324 """Dump all files or providers matching a glob expression"""
325 what, globexpr = params
326 if what == "files":
327 self._checkParsed()
328 for key in globfilter( cooker.status.pkg_fn.keys(), globexpr ): print key
329 elif what == "providers":
330 self._checkParsed()
331 for key in globfilter( cooker.status.pkg_pn.keys(), globexpr ): print key
332 else:
333 print "Usage: match %s" % self.print_.usage
334 match.usage = "<files|providers> <glob>"
335
336 def new( self, params ):
337 """Create a new .bb file and open the editor"""
338 dirname, filename = params
339 packages = '/'.join( data.getVar( "BBFILES", cooker.configuration.data, 1 ).split('/')[:-2] )
340 fulldirname = "%s/%s" % ( packages, dirname )
341
342 if not os.path.exists( fulldirname ):
343 print "SHELL: Creating '%s'" % fulldirname
344 os.mkdir( fulldirname )
345 if os.path.exists( fulldirname ) and os.path.isdir( fulldirname ):
346 if os.path.exists( "%s/%s" % ( fulldirname, filename ) ):
347 print "SHELL: ERROR: %s/%s already exists" % ( fulldirname, filename )
348 return False
349 print "SHELL: Creating '%s/%s'" % ( fulldirname, filename )
350 newpackage = open( "%s/%s" % ( fulldirname, filename ), "w" )
351 print >>newpackage,"""DESCRIPTION = ""
352SECTION = ""
353AUTHOR = ""
354HOMEPAGE = ""
355MAINTAINER = ""
356LICENSE = "GPL"
357PR = "r0"
358
359SRC_URI = ""
360
361#inherit base
362
363#do_configure() {
364#
365#}
366
367#do_compile() {
368#
369#}
370
371#do_stage() {
372#
373#}
374
375#do_install() {
376#
377#}
378"""
379 newpackage.close()
380 os.system( "%s %s/%s" % ( os.environ.get( "EDITOR" ), fulldirname, filename ) )
381 new.usage = "<directory> <filename>"
382
383 def package( self, params ):
384 """Execute 'package' on a providee"""
385 self.build( params, "package" )
386 package.usage = "<providee>"
387
388 def pasteBin( self, params ):
389 """Send a command + output buffer to the pastebin at http://rafb.net/paste"""
390 index = params[0]
391 contents = self._shell.myout.buffer( int( index ) )
392 sendToPastebin( "output of " + params[0], contents )
393 pasteBin.usage = "<index>"
394
395 def pasteLog( self, params ):
396 """Send the last event exception error log (if there is one) to http://rafb.net/paste"""
397 if last_exception is None:
398 print "SHELL: No Errors yet (Phew)..."
399 else:
400 reason, event = last_exception.args
401 print "SHELL: Reason for the last error: '%s'" % reason
402 if ':' in reason:
403 msg, filename = reason.split( ':' )
404 filename = filename.strip()
405 print "SHELL: Pasting log file to pastebin..."
406
407 file = open( filename ).read()
408 sendToPastebin( "contents of " + filename, file )
409
410 def patch( self, params ):
411 """Execute 'patch' command on a providee"""
412 self.build( params, "patch" )
413 patch.usage = "<providee>"
414
415 def parse( self, params ):
416 """(Re-)parse .bb files and calculate the dependency graph"""
417 cooker.status = cache.CacheData()
418 ignore = data.getVar("ASSUME_PROVIDED", cooker.configuration.data, 1) or ""
419 cooker.status.ignored_dependencies = set( ignore.split() )
420 cooker.handleCollections( data.getVar("BBFILE_COLLECTIONS", cooker.configuration.data, 1) )
421
422 (filelist, masked) = cooker.collect_bbfiles()
423 cooker.parse_bbfiles(filelist, masked, cooker.myProgressCallback)
424 cooker.buildDepgraph()
425 global parsed
426 parsed = True
427 print
428
429 def reparse( self, params ):
430 """(re)Parse a providee's bb file"""
431 bbfile = self._findProvider( params[0] )
432 if bbfile is not None:
433 print "SHELL: Found bbfile '%s' for '%s'" % ( bbfile, params[0] )
434 self.fileReparse( [ bbfile ] )
435 else:
436 print "ERROR: Nothing provides '%s'" % params[0]
437 reparse.usage = "<providee>"
438
439 def getvar( self, params ):
440 """Dump the contents of an outer BitBake environment variable"""
441 var = params[0]
442 value = data.getVar( var, cooker.configuration.data, 1 )
443 print value
444 getvar.usage = "<variable>"
445
446 def peek( self, params ):
447 """Dump contents of variable defined in providee's metadata"""
448 name, var = params
449 bbfile = self._findProvider( name )
450 if bbfile is not None:
451 the_data = cooker.bb_cache.loadDataFull(bbfile, cooker.configuration.data)
452 value = the_data.getVar( var, 1 )
453 print value
454 else:
455 print "ERROR: Nothing provides '%s'" % name
456 peek.usage = "<providee> <variable>"
457
458 def poke( self, params ):
459 """Set contents of variable defined in providee's metadata"""
460 name, var, value = params
461 bbfile = self._findProvider( name )
462 if bbfile is not None:
463 print "ERROR: Sorry, this functionality is currently broken"
464 #d = cooker.pkgdata[bbfile]
465 #data.setVar( var, value, d )
466
467 # mark the change semi persistant
468 #cooker.pkgdata.setDirty(bbfile, d)
469 #print "OK"
470 else:
471 print "ERROR: Nothing provides '%s'" % name
472 poke.usage = "<providee> <variable> <value>"
473
474 def print_( self, params ):
475 """Dump all files or providers"""
476 what = params[0]
477 if what == "files":
478 self._checkParsed()
479 for key in cooker.status.pkg_fn.keys(): print key
480 elif what == "providers":
481 self._checkParsed()
482 for key in cooker.status.providers.keys(): print key
483 else:
484 print "Usage: print %s" % self.print_.usage
485 print_.usage = "<files|providers>"
486
487 def python( self, params ):
488 """Enter the expert mode - an interactive BitBake Python Interpreter"""
489 sys.ps1 = "EXPERT BB>>> "
490 sys.ps2 = "EXPERT BB... "
491 import code
492 interpreter = code.InteractiveConsole( dict( globals() ) )
493 interpreter.interact( "SHELL: Expert Mode - BitBake Python %s\nType 'help' for more information, press CTRL-D to switch back to BBSHELL." % sys.version )
494
495 def showdata( self, params ):
496 """Execute 'showdata' on a providee"""
497 cooker.showEnvironment(None, params)
498 showdata.usage = "<providee>"
499
500 def setVar( self, params ):
501 """Set an outer BitBake environment variable"""
502 var, value = params
503 data.setVar( var, value, cooker.configuration.data )
504 print "OK"
505 setVar.usage = "<variable> <value>"
506
507 def rebuild( self, params ):
508 """Clean and rebuild a .bb file or a providee"""
509 self.build( params, "clean" )
510 self.build( params, "build" )
511 rebuild.usage = "<providee>"
512
513 def shell( self, params ):
514 """Execute a shell command and dump the output"""
515 if params != "":
516 print commands.getoutput( " ".join( params ) )
517 shell.usage = "<...>"
518
519 def stage( self, params ):
520 """Execute 'stage' on a providee"""
521 self.build( params, "stage" )
522 stage.usage = "<providee>"
523
524 def status( self, params ):
525 """<just for testing>"""
526 print "-" * 78
527 print "building list = '%s'" % cooker.building_list
528 print "build path = '%s'" % cooker.build_path
529 print "consider_msgs_cache = '%s'" % cooker.consider_msgs_cache
530 print "build stats = '%s'" % cooker.stats
531 if last_exception is not None: print "last_exception = '%s'" % repr( last_exception.args )
532 print "memory output contents = '%s'" % self._shell.myout._buffer
533
534 def test( self, params ):
535 """<just for testing>"""
536 print "testCommand called with '%s'" % params
537
538 def unpack( self, params ):
539 """Execute 'unpack' on a providee"""
540 self.build( params, "unpack" )
541 unpack.usage = "<providee>"
542
543 def which( self, params ):
544 """Computes the providers for a given providee"""
545 # Need to use taskData for this information
546 item = params[0]
547
548 self._checkParsed()
549
550 preferred = data.getVar( "PREFERRED_PROVIDER_%s" % item, cooker.configuration.data, 1 )
551 if not preferred: preferred = item
552
553 try:
554 lv, lf, pv, pf = Providers.findBestProvider(preferred, cooker.configuration.data, cooker.status)
555 except KeyError:
556 lv, lf, pv, pf = (None,)*4
557
558 try:
559 providers = cooker.status.providers[item]
560 except KeyError:
561 print "SHELL: ERROR: Nothing provides", preferred
562 else:
563 for provider in providers:
564 if provider == pf: provider = " (***) %s" % provider
565 else: provider = " %s" % provider
566 print provider
567 which.usage = "<providee>"
568
569##########################################################################
570# Common helper functions
571##########################################################################
572
573def completeFilePath( bbfile ):
574 """Get the complete bbfile path"""
575 if not cooker.status: return bbfile
576 if not cooker.status.pkg_fn: return bbfile
577 for key in cooker.status.pkg_fn.keys():
578 if key.endswith( bbfile ):
579 return key
580 return bbfile
581
582def sendToPastebin( desc, content ):
583 """Send content to http://oe.pastebin.com"""
584 mydata = {}
585 mydata["lang"] = "Plain Text"
586 mydata["desc"] = desc
587 mydata["cvt_tabs"] = "No"
588 mydata["nick"] = "%s@%s" % ( os.environ.get( "USER", "unknown" ), socket.gethostname() or "unknown" )
589 mydata["text"] = content
590 params = urllib.urlencode( mydata )
591 headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain"}
592
593 host = "rafb.net"
594 conn = httplib.HTTPConnection( "%s:80" % host )
595 conn.request("POST", "/paste/paste.php", params, headers )
596
597 response = conn.getresponse()
598 conn.close()
599
600 if response.status == 302:
601 location = response.getheader( "location" ) or "unknown"
602 print "SHELL: Pasted to http://%s%s" % ( host, location )
603 else:
604 print "ERROR: %s %s" % ( response.status, response.reason )
605
606def completer( text, state ):
607 """Return a possible readline completion"""
608 debugOut( "completer called with text='%s', state='%d'" % ( text, state ) )
609
610 if state == 0:
611 line = readline.get_line_buffer()
612 if " " in line:
613 line = line.split()
614 # we are in second (or more) argument
615 if line[0] in cmds and hasattr( cmds[line[0]][0], "usage" ): # known command and usage
616 u = getattr( cmds[line[0]][0], "usage" ).split()[0]
617 if u == "<variable>":
618 allmatches = cooker.configuration.data.keys()
619 elif u == "<bbfile>":
620 if cooker.status.pkg_fn is None: allmatches = [ "(No Matches Available. Parsed yet?)" ]
621 else: allmatches = [ x.split("/")[-1] for x in cooker.status.pkg_fn.keys() ]
622 elif u == "<providee>":
623 if cooker.status.pkg_fn is None: allmatches = [ "(No Matches Available. Parsed yet?)" ]
624 else: allmatches = cooker.status.providers.iterkeys()
625 else: allmatches = [ "(No tab completion available for this command)" ]
626 else: allmatches = [ "(No tab completion available for this command)" ]
627 else:
628 # we are in first argument
629 allmatches = cmds.iterkeys()
630
631 completer.matches = [ x for x in allmatches if x[:len(text)] == text ]
632 #print "completer.matches = '%s'" % completer.matches
633 if len( completer.matches ) > state:
634 return completer.matches[state]
635 else:
636 return None
637
638def debugOut( text ):
639 if debug:
640 sys.stderr.write( "( %s )\n" % text )
641
642def columnize( alist, width = 80 ):
643 """
644 A word-wrap function that preserves existing line breaks
645 and most spaces in the text. Expects that existing line
646 breaks are posix newlines (\n).
647 """
648 return reduce(lambda line, word, width=width: '%s%s%s' %
649 (line,
650 ' \n'[(len(line[line.rfind('\n')+1:])
651 + len(word.split('\n',1)[0]
652 ) >= width)],
653 word),
654 alist
655 )
656
657def globfilter( names, pattern ):
658 return fnmatch.filter( names, pattern )
659
660##########################################################################
661# Class MemoryOutput
662##########################################################################
663
664class MemoryOutput:
665 """File-like output class buffering the output of the last 10 commands"""
666 def __init__( self, delegate ):
667 self.delegate = delegate
668 self._buffer = []
669 self.text = []
670 self._command = None
671
672 def startCommand( self, command ):
673 self._command = command
674 self.text = []
675 def endCommand( self ):
676 if self._command is not None:
677 if len( self._buffer ) == 10: del self._buffer[0]
678 self._buffer.append( ( self._command, self.text ) )
679 def removeLast( self ):
680 if self._buffer:
681 del self._buffer[ len( self._buffer ) - 1 ]
682 self.text = []
683 self._command = None
684 def lastBuffer( self ):
685 if self._buffer:
686 return self._buffer[ len( self._buffer ) -1 ][1]
687 def bufferedCommands( self ):
688 return [ cmd for cmd, output in self._buffer ]
689 def buffer( self, i ):
690 if i < len( self._buffer ):
691 return "BB>> %s\n%s" % ( self._buffer[i][0], "".join( self._buffer[i][1] ) )
692 else: return "ERROR: Invalid buffer number. Buffer needs to be in (0, %d)" % ( len( self._buffer ) - 1 )
693 def write( self, text ):
694 if self._command is not None and text != "BB>> ": self.text.append( text )
695 if self.delegate is not None: self.delegate.write( text )
696 def flush( self ):
697 return self.delegate.flush()
698 def fileno( self ):
699 return self.delegate.fileno()
700 def isatty( self ):
701 return self.delegate.isatty()
702
703##########################################################################
704# Class BitBakeShell
705##########################################################################
706
707class BitBakeShell:
708
709 def __init__( self ):
710 """Register commands and set up readline"""
711 self.commandQ = Queue.Queue()
712 self.commands = BitBakeShellCommands( self )
713 self.myout = MemoryOutput( sys.stdout )
714 self.historyfilename = os.path.expanduser( "~/.bbsh_history" )
715 self.startupfilename = os.path.expanduser( "~/.bbsh_startup" )
716
717 readline.set_completer( completer )
718 readline.set_completer_delims( " " )
719 readline.parse_and_bind("tab: complete")
720
721 try:
722 readline.read_history_file( self.historyfilename )
723 except IOError:
724 pass # It doesn't exist yet.
725
726 print __credits__
727
728 def cleanup( self ):
729 """Write readline history and clean up resources"""
730 debugOut( "writing command history" )
731 try:
732 readline.write_history_file( self.historyfilename )
733 except:
734 print "SHELL: Unable to save command history"
735
736 def registerCommand( self, command, function, numparams = 0, usage = "", helptext = "" ):
737 """Register a command"""
738 if usage == "": usage = command
739 if helptext == "": helptext = function.__doc__ or "<not yet documented>"
740 cmds[command] = ( function, numparams, usage, helptext )
741
742 def processCommand( self, command, params ):
743 """Process a command. Check number of params and print a usage string, if appropriate"""
744 debugOut( "processing command '%s'..." % command )
745 try:
746 function, numparams, usage, helptext = cmds[command]
747 except KeyError:
748 print "SHELL: ERROR: '%s' command is not a valid command." % command
749 self.myout.removeLast()
750 else:
751 if (numparams != -1) and (not len( params ) == numparams):
752 print "Usage: '%s'" % usage
753 return
754
755 result = function( self.commands, params )
756 debugOut( "result was '%s'" % result )
757
758 def processStartupFile( self ):
759 """Read and execute all commands found in $HOME/.bbsh_startup"""
760 if os.path.exists( self.startupfilename ):
761 startupfile = open( self.startupfilename, "r" )
762 for cmdline in startupfile:
763 debugOut( "processing startup line '%s'" % cmdline )
764 if not cmdline:
765 continue
766 if "|" in cmdline:
767 print "ERROR: '|' in startup file is not allowed. Ignoring line"
768 continue
769 self.commandQ.put( cmdline.strip() )
770
771 def main( self ):
772 """The main command loop"""
773 while not leave_mainloop:
774 try:
775 if self.commandQ.empty():
776 sys.stdout = self.myout.delegate
777 cmdline = raw_input( "BB>> " )
778 sys.stdout = self.myout
779 else:
780 cmdline = self.commandQ.get()
781 if cmdline:
782 allCommands = cmdline.split( ';' )
783 for command in allCommands:
784 pipecmd = None
785 #
786 # special case for expert mode
787 if command == 'python':
788 sys.stdout = self.myout.delegate
789 self.processCommand( command, "" )
790 sys.stdout = self.myout
791 else:
792 self.myout.startCommand( command )
793 if '|' in command: # disable output
794 command, pipecmd = command.split( '|' )
795 delegate = self.myout.delegate
796 self.myout.delegate = None
797 tokens = shlex.split( command, True )
798 self.processCommand( tokens[0], tokens[1:] or "" )
799 self.myout.endCommand()
800 if pipecmd is not None: # restore output
801 self.myout.delegate = delegate
802
803 pipe = popen2.Popen4( pipecmd )
804 pipe.tochild.write( "\n".join( self.myout.lastBuffer() ) )
805 pipe.tochild.close()
806 sys.stdout.write( pipe.fromchild.read() )
807 #
808 except EOFError:
809 print
810 return
811 except KeyboardInterrupt:
812 print
813
814##########################################################################
815# Start function - called from the BitBake command line utility
816##########################################################################
817
818def start( aCooker ):
819 global cooker
820 cooker = aCooker
821 bbshell = BitBakeShell()
822 bbshell.processStartupFile()
823 bbshell.main()
824 bbshell.cleanup()
825
826if __name__ == "__main__":
827 print "SHELL: Sorry, this program should only be called by BitBake."