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