blob: 1dd8d54bdb1168cdf8e284f88e9fe59b87d2ba24 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# 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
55from __future__ import print_function
56from functools import reduce
57try:
58 set
59except NameError:
60 from sets import Set as set
61import sys, os, readline, socket, httplib, urllib, commands, popen2, shlex, Queue, fnmatch
62from bb import data, parse, build, cache, taskdata, runqueue, providers as Providers
63
64__version__ = "0.5.3.1"
65__credits__ = """BitBake Shell Version %s (C) 2005 Michael 'Mickey' Lauer <mickey@Vanille.de>
66Type 'help' for more information, press CTRL-D to exit.""" % __version__
67
68cmds = {}
69leave_mainloop = False
70last_exception = None
71cooker = None
72parsed = False
73debug = os.environ.get( "BBSHELL_DEBUG", "" )
74
75##########################################################################
76# Class BitBakeShellCommands
77##########################################################################
78
79class BitBakeShellCommands:
80 """This class contains the valid commands for the shell"""
81
82 def __init__( self, shell ):
83 """Register all the commands"""
84 self._shell = shell
85 for attr in BitBakeShellCommands.__dict__:
86 if not attr.startswith( "_" ):
87 if attr.endswith( "_" ):
88 command = attr[:-1].lower()
89 else:
90 command = attr[:].lower()
91 method = getattr( BitBakeShellCommands, attr )
92 debugOut( "registering command '%s'" % command )
93 # scan number of arguments
94 usage = getattr( method, "usage", "" )
95 if usage != "<...>":
96 numArgs = len( usage.split() )
97 else:
98 numArgs = -1
99 shell.registerCommand( command, method, numArgs, "%s %s" % ( command, usage ), method.__doc__ )
100
101 def _checkParsed( self ):
102 if not parsed:
103 print("SHELL: This command needs to parse bbfiles...")
104 self.parse( None )
105
106 def _findProvider( self, item ):
107 self._checkParsed()
108 # Need to use taskData for this information
109 preferred = data.getVar( "PREFERRED_PROVIDER_%s" % item, cooker.configuration.data, 1 )
110 if not preferred: preferred = item
111 try:
112 lv, lf, pv, pf = Providers.findBestProvider(preferred, cooker.configuration.data, cooker.status)
113 except KeyError:
114 if item in cooker.status.providers:
115 pf = cooker.status.providers[item][0]
116 else:
117 pf = None
118 return pf
119
120 def alias( self, params ):
121 """Register a new name for a command"""
122 new, old = params
123 if not old in cmds:
124 print("ERROR: Command '%s' not known" % old)
125 else:
126 cmds[new] = cmds[old]
127 print("OK")
128 alias.usage = "<alias> <command>"
129
130 def buffer( self, params ):
131 """Dump specified output buffer"""
132 index = params[0]
133 print(self._shell.myout.buffer( int( index ) ))
134 buffer.usage = "<index>"
135
136 def buffers( self, params ):
137 """Show the available output buffers"""
138 commands = self._shell.myout.bufferedCommands()
139 if not commands:
140 print("SHELL: No buffered commands available yet. Start doing something.")
141 else:
142 print("="*35, "Available Output Buffers", "="*27)
143 for index, cmd in enumerate( commands ):
144 print("| %s %s" % ( str( index ).ljust( 3 ), cmd ))
145 print("="*88)
146
147 def build( self, params, cmd = "build" ):
148 """Build a providee"""
149 global last_exception
150 globexpr = params[0]
151 self._checkParsed()
152 names = globfilter( cooker.status.pkg_pn, globexpr )
153 if len( names ) == 0: names = [ globexpr ]
154 print("SHELL: Building %s" % ' '.join( names ))
155
156 td = taskdata.TaskData(cooker.configuration.abort)
157 localdata = data.createCopy(cooker.configuration.data)
158 data.update_data(localdata)
159 data.expandKeys(localdata)
160
161 try:
162 tasks = []
163 for name in names:
164 td.add_provider(localdata, cooker.status, name)
165 providers = td.get_provider(name)
166
167 if len(providers) == 0:
168 raise Providers.NoProvider
169
170 tasks.append([name, "do_%s" % cmd])
171
172 td.add_unresolved(localdata, cooker.status)
173
174 rq = runqueue.RunQueue(cooker, localdata, cooker.status, td, tasks)
175 rq.prepare_runqueue()
176 rq.execute_runqueue()
177
178 except Providers.NoProvider:
179 print("ERROR: No Provider")
180 last_exception = Providers.NoProvider
181
182 except runqueue.TaskFailure as fnids:
183 last_exception = runqueue.TaskFailure
184
185 except build.FuncFailed as 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.FuncFailed as 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.parser.reparse(bbfile)
276 if False: #fromCache:
277 print("SHELL: File has not been updated, not reparsing")
278 else:
279 print("SHELL: Parsed")
280 fileReparse.usage = "<bbfile>"
281
282 def abort( self, params ):
283 """Toggle abort task execution flag (see bitbake -k)"""
284 cooker.configuration.abort = not cooker.configuration.abort
285 print("SHELL: Abort Flag is now '%s'" % repr( cooker.configuration.abort ))
286
287 def force( self, params ):
288 """Toggle force task execution flag (see bitbake -f)"""
289 cooker.configuration.force = not cooker.configuration.force
290 print("SHELL: Force Flag is now '%s'" % repr( cooker.configuration.force ))
291
292 def help( self, params ):
293 """Show a comprehensive list of commands and their purpose"""
294 print("="*30, "Available Commands", "="*30)
295 for cmd in sorted(cmds):
296 function, numparams, usage, helptext = cmds[cmd]
297 print("| %s | %s" % (usage.ljust(30), helptext))
298 print("="*78)
299
300 def lastError( self, params ):
301 """Show the reason or log that was produced by the last BitBake event exception"""
302 if last_exception is None:
303 print("SHELL: No Errors yet (Phew)...")
304 else:
305 reason, event = last_exception.args
306 print("SHELL: Reason for the last error: '%s'" % reason)
307 if ':' in reason:
308 msg, filename = reason.split( ':' )
309 filename = filename.strip()
310 print("SHELL: Dumping log file for last error:")
311 try:
312 print(open( filename ).read())
313 except IOError:
314 print("ERROR: Couldn't open '%s'" % filename)
315
316 def match( self, params ):
317 """Dump all files or providers matching a glob expression"""
318 what, globexpr = params
319 if what == "files":
320 self._checkParsed()
321 for key in globfilter( cooker.status.pkg_fn, globexpr ): print(key)
322 elif what == "providers":
323 self._checkParsed()
324 for key in globfilter( cooker.status.pkg_pn, globexpr ): print(key)
325 else:
326 print("Usage: match %s" % self.print_.usage)
327 match.usage = "<files|providers> <glob>"
328
329 def new( self, params ):
330 """Create a new .bb file and open the editor"""
331 dirname, filename = params
332 packages = '/'.join( data.getVar( "BBFILES", cooker.configuration.data, 1 ).split('/')[:-2] )
333 fulldirname = "%s/%s" % ( packages, dirname )
334
335 if not os.path.exists( fulldirname ):
336 print("SHELL: Creating '%s'" % fulldirname)
337 os.mkdir( fulldirname )
338 if os.path.exists( fulldirname ) and os.path.isdir( fulldirname ):
339 if os.path.exists( "%s/%s" % ( fulldirname, filename ) ):
340 print("SHELL: ERROR: %s/%s already exists" % ( fulldirname, filename ))
341 return False
342 print("SHELL: Creating '%s/%s'" % ( fulldirname, filename ))
343 newpackage = open( "%s/%s" % ( fulldirname, filename ), "w" )
344 print("""DESCRIPTION = ""
345SECTION = ""
346AUTHOR = ""
347HOMEPAGE = ""
348MAINTAINER = ""
349LICENSE = "GPL"
350PR = "r0"
351
352SRC_URI = ""
353
354#inherit base
355
356#do_configure() {
357#
358#}
359
360#do_compile() {
361#
362#}
363
364#do_stage() {
365#
366#}
367
368#do_install() {
369#
370#}
371""", file=newpackage)
372 newpackage.close()
373 os.system( "%s %s/%s" % ( os.environ.get( "EDITOR" ), fulldirname, filename ) )
374 new.usage = "<directory> <filename>"
375
376 def package( self, params ):
377 """Execute 'package' on a providee"""
378 self.build( params, "package" )
379 package.usage = "<providee>"
380
381 def pasteBin( self, params ):
382 """Send a command + output buffer to the pastebin at http://rafb.net/paste"""
383 index = params[0]
384 contents = self._shell.myout.buffer( int( index ) )
385 sendToPastebin( "output of " + params[0], contents )
386 pasteBin.usage = "<index>"
387
388 def pasteLog( self, params ):
389 """Send the last event exception error log (if there is one) to http://rafb.net/paste"""
390 if last_exception is None:
391 print("SHELL: No Errors yet (Phew)...")
392 else:
393 reason, event = last_exception.args
394 print("SHELL: Reason for the last error: '%s'" % reason)
395 if ':' in reason:
396 msg, filename = reason.split( ':' )
397 filename = filename.strip()
398 print("SHELL: Pasting log file to pastebin...")
399
400 file = open( filename ).read()
401 sendToPastebin( "contents of " + filename, file )
402
403 def patch( self, params ):
404 """Execute 'patch' command on a providee"""
405 self.build( params, "patch" )
406 patch.usage = "<providee>"
407
408 def parse( self, params ):
409 """(Re-)parse .bb files and calculate the dependency graph"""
410 cooker.status = cache.CacheData(cooker.caches_array)
411 ignore = data.getVar("ASSUME_PROVIDED", cooker.configuration.data, 1) or ""
412 cooker.status.ignored_dependencies = set( ignore.split() )
413 cooker.handleCollections( data.getVar("BBFILE_COLLECTIONS", cooker.configuration.data, 1) )
414
415 (filelist, masked) = cooker.collect_bbfiles()
416 cooker.parse_bbfiles(filelist, masked, cooker.myProgressCallback)
417 cooker.buildDepgraph()
418 global parsed
419 parsed = True
420 print()
421
422 def reparse( self, params ):
423 """(re)Parse a providee's bb file"""
424 bbfile = self._findProvider( params[0] )
425 if bbfile is not None:
426 print("SHELL: Found bbfile '%s' for '%s'" % ( bbfile, params[0] ))
427 self.fileReparse( [ bbfile ] )
428 else:
429 print("ERROR: Nothing provides '%s'" % params[0])
430 reparse.usage = "<providee>"
431
432 def getvar( self, params ):
433 """Dump the contents of an outer BitBake environment variable"""
434 var = params[0]
435 value = data.getVar( var, cooker.configuration.data, 1 )
436 print(value)
437 getvar.usage = "<variable>"
438
439 def peek( self, params ):
440 """Dump contents of variable defined in providee's metadata"""
441 name, var = params
442 bbfile = self._findProvider( name )
443 if bbfile is not None:
444 the_data = cache.Cache.loadDataFull(bbfile, cooker.configuration.data)
445 value = the_data.getVar( var, 1 )
446 print(value)
447 else:
448 print("ERROR: Nothing provides '%s'" % name)
449 peek.usage = "<providee> <variable>"
450
451 def poke( self, params ):
452 """Set contents of variable defined in providee's metadata"""
453 name, var, value = params
454 bbfile = self._findProvider( name )
455 if bbfile is not None:
456 print("ERROR: Sorry, this functionality is currently broken")
457 #d = cooker.pkgdata[bbfile]
458 #data.setVar( var, value, d )
459
460 # mark the change semi persistant
461 #cooker.pkgdata.setDirty(bbfile, d)
462 #print "OK"
463 else:
464 print("ERROR: Nothing provides '%s'" % name)
465 poke.usage = "<providee> <variable> <value>"
466
467 def print_( self, params ):
468 """Dump all files or providers"""
469 what = params[0]
470 if what == "files":
471 self._checkParsed()
472 for key in cooker.status.pkg_fn: print(key)
473 elif what == "providers":
474 self._checkParsed()
475 for key in cooker.status.providers: print(key)
476 else:
477 print("Usage: print %s" % self.print_.usage)
478 print_.usage = "<files|providers>"
479
480 def python( self, params ):
481 """Enter the expert mode - an interactive BitBake Python Interpreter"""
482 sys.ps1 = "EXPERT BB>>> "
483 sys.ps2 = "EXPERT BB... "
484 import code
485 interpreter = code.InteractiveConsole( dict( globals() ) )
486 interpreter.interact( "SHELL: Expert Mode - BitBake Python %s\nType 'help' for more information, press CTRL-D to switch back to BBSHELL." % sys.version )
487
488 def showdata( self, params ):
489 """Execute 'showdata' on a providee"""
490 cooker.showEnvironment(None, params)
491 showdata.usage = "<providee>"
492
493 def setVar( self, params ):
494 """Set an outer BitBake environment variable"""
495 var, value = params
496 data.setVar( var, value, cooker.configuration.data )
497 print("OK")
498 setVar.usage = "<variable> <value>"
499
500 def rebuild( self, params ):
501 """Clean and rebuild a .bb file or a providee"""
502 self.build( params, "clean" )
503 self.build( params, "build" )
504 rebuild.usage = "<providee>"
505
506 def shell( self, params ):
507 """Execute a shell command and dump the output"""
508 if params != "":
509 print(commands.getoutput( " ".join( params ) ))
510 shell.usage = "<...>"
511
512 def stage( self, params ):
513 """Execute 'stage' on a providee"""
514 self.build( params, "populate_staging" )
515 stage.usage = "<providee>"
516
517 def status( self, params ):
518 """<just for testing>"""
519 print("-" * 78)
520 print("building list = '%s'" % cooker.building_list)
521 print("build path = '%s'" % cooker.build_path)
522 print("consider_msgs_cache = '%s'" % cooker.consider_msgs_cache)
523 print("build stats = '%s'" % cooker.stats)
524 if last_exception is not None: print("last_exception = '%s'" % repr( last_exception.args ))
525 print("memory output contents = '%s'" % self._shell.myout._buffer)
526
527 def test( self, params ):
528 """<just for testing>"""
529 print("testCommand called with '%s'" % params)
530
531 def unpack( self, params ):
532 """Execute 'unpack' on a providee"""
533 self.build( params, "unpack" )
534 unpack.usage = "<providee>"
535
536 def which( self, params ):
537 """Computes the providers for a given providee"""
538 # Need to use taskData for this information
539 item = params[0]
540
541 self._checkParsed()
542
543 preferred = data.getVar( "PREFERRED_PROVIDER_%s" % item, cooker.configuration.data, 1 )
544 if not preferred: preferred = item
545
546 try:
547 lv, lf, pv, pf = Providers.findBestProvider(preferred, cooker.configuration.data, cooker.status)
548 except KeyError:
549 lv, lf, pv, pf = (None,)*4
550
551 try:
552 providers = cooker.status.providers[item]
553 except KeyError:
554 print("SHELL: ERROR: Nothing provides", preferred)
555 else:
556 for provider in providers:
557 if provider == pf: provider = " (***) %s" % provider
558 else: provider = " %s" % provider
559 print(provider)
560 which.usage = "<providee>"
561
562##########################################################################
563# Common helper functions
564##########################################################################
565
566def completeFilePath( bbfile ):
567 """Get the complete bbfile path"""
568 if not cooker.status: return bbfile
569 if not cooker.status.pkg_fn: return bbfile
570 for key in cooker.status.pkg_fn:
571 if key.endswith( bbfile ):
572 return key
573 return bbfile
574
575def sendToPastebin( desc, content ):
576 """Send content to http://oe.pastebin.com"""
577 mydata = {}
578 mydata["lang"] = "Plain Text"
579 mydata["desc"] = desc
580 mydata["cvt_tabs"] = "No"
581 mydata["nick"] = "%s@%s" % ( os.environ.get( "USER", "unknown" ), socket.gethostname() or "unknown" )
582 mydata["text"] = content
583 params = urllib.urlencode( mydata )
584 headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
585
586 host = "rafb.net"
587 conn = httplib.HTTPConnection( "%s:80" % host )
588 conn.request("POST", "/paste/paste.php", params, headers )
589
590 response = conn.getresponse()
591 conn.close()
592
593 if response.status == 302:
594 location = response.getheader( "location" ) or "unknown"
595 print("SHELL: Pasted to http://%s%s" % ( host, location ))
596 else:
597 print("ERROR: %s %s" % ( response.status, response.reason ))
598
599def completer( text, state ):
600 """Return a possible readline completion"""
601 debugOut( "completer called with text='%s', state='%d'" % ( text, state ) )
602
603 if state == 0:
604 line = readline.get_line_buffer()
605 if " " in line:
606 line = line.split()
607 # we are in second (or more) argument
608 if line[0] in cmds and hasattr( cmds[line[0]][0], "usage" ): # known command and usage
609 u = getattr( cmds[line[0]][0], "usage" ).split()[0]
610 if u == "<variable>":
611 allmatches = cooker.configuration.data.keys()
612 elif u == "<bbfile>":
613 if cooker.status.pkg_fn is None: allmatches = [ "(No Matches Available. Parsed yet?)" ]
614 else: allmatches = [ x.split("/")[-1] for x in cooker.status.pkg_fn ]
615 elif u == "<providee>":
616 if cooker.status.pkg_fn is None: allmatches = [ "(No Matches Available. Parsed yet?)" ]
617 else: allmatches = cooker.status.providers.iterkeys()
618 else: allmatches = [ "(No tab completion available for this command)" ]
619 else: allmatches = [ "(No tab completion available for this command)" ]
620 else:
621 # we are in first argument
622 allmatches = cmds.iterkeys()
623
624 completer.matches = [ x for x in allmatches if x[:len(text)] == text ]
625 #print "completer.matches = '%s'" % completer.matches
626 if len( completer.matches ) > state:
627 return completer.matches[state]
628 else:
629 return None
630
631def debugOut( text ):
632 if debug:
633 sys.stderr.write( "( %s )\n" % text )
634
635def columnize( alist, width = 80 ):
636 """
637 A word-wrap function that preserves existing line breaks
638 and most spaces in the text. Expects that existing line
639 breaks are posix newlines (\n).
640 """
641 return reduce(lambda line, word, width=width: '%s%s%s' %
642 (line,
643 ' \n'[(len(line[line.rfind('\n')+1:])
644 + len(word.split('\n', 1)[0]
645 ) >= width)],
646 word),
647 alist
648 )
649
650def globfilter( names, pattern ):
651 return fnmatch.filter( names, pattern )
652
653##########################################################################
654# Class MemoryOutput
655##########################################################################
656
657class MemoryOutput:
658 """File-like output class buffering the output of the last 10 commands"""
659 def __init__( self, delegate ):
660 self.delegate = delegate
661 self._buffer = []
662 self.text = []
663 self._command = None
664
665 def startCommand( self, command ):
666 self._command = command
667 self.text = []
668 def endCommand( self ):
669 if self._command is not None:
670 if len( self._buffer ) == 10: del self._buffer[0]
671 self._buffer.append( ( self._command, self.text ) )
672 def removeLast( self ):
673 if self._buffer:
674 del self._buffer[ len( self._buffer ) - 1 ]
675 self.text = []
676 self._command = None
677 def lastBuffer( self ):
678 if self._buffer:
679 return self._buffer[ len( self._buffer ) -1 ][1]
680 def bufferedCommands( self ):
681 return [ cmd for cmd, output in self._buffer ]
682 def buffer( self, i ):
683 if i < len( self._buffer ):
684 return "BB>> %s\n%s" % ( self._buffer[i][0], "".join( self._buffer[i][1] ) )
685 else: return "ERROR: Invalid buffer number. Buffer needs to be in (0, %d)" % ( len( self._buffer ) - 1 )
686 def write( self, text ):
687 if self._command is not None and text != "BB>> ": self.text.append( text )
688 if self.delegate is not None: self.delegate.write( text )
689 def flush( self ):
690 return self.delegate.flush()
691 def fileno( self ):
692 return self.delegate.fileno()
693 def isatty( self ):
694 return self.delegate.isatty()
695
696##########################################################################
697# Class BitBakeShell
698##########################################################################
699
700class BitBakeShell:
701
702 def __init__( self ):
703 """Register commands and set up readline"""
704 self.commandQ = Queue.Queue()
705 self.commands = BitBakeShellCommands( self )
706 self.myout = MemoryOutput( sys.stdout )
707 self.historyfilename = os.path.expanduser( "~/.bbsh_history" )
708 self.startupfilename = os.path.expanduser( "~/.bbsh_startup" )
709
710 readline.set_completer( completer )
711 readline.set_completer_delims( " " )
712 readline.parse_and_bind("tab: complete")
713
714 try:
715 readline.read_history_file( self.historyfilename )
716 except IOError:
717 pass # It doesn't exist yet.
718
719 print(__credits__)
720
721 def cleanup( self ):
722 """Write readline history and clean up resources"""
723 debugOut( "writing command history" )
724 try:
725 readline.write_history_file( self.historyfilename )
726 except:
727 print("SHELL: Unable to save command history")
728
729 def registerCommand( self, command, function, numparams = 0, usage = "", helptext = "" ):
730 """Register a command"""
731 if usage == "": usage = command
732 if helptext == "": helptext = function.__doc__ or "<not yet documented>"
733 cmds[command] = ( function, numparams, usage, helptext )
734
735 def processCommand( self, command, params ):
736 """Process a command. Check number of params and print a usage string, if appropriate"""
737 debugOut( "processing command '%s'..." % command )
738 try:
739 function, numparams, usage, helptext = cmds[command]
740 except KeyError:
741 print("SHELL: ERROR: '%s' command is not a valid command." % command)
742 self.myout.removeLast()
743 else:
744 if (numparams != -1) and (not len( params ) == numparams):
745 print("Usage: '%s'" % usage)
746 return
747
748 result = function( self.commands, params )
749 debugOut( "result was '%s'" % result )
750
751 def processStartupFile( self ):
752 """Read and execute all commands found in $HOME/.bbsh_startup"""
753 if os.path.exists( self.startupfilename ):
754 startupfile = open( self.startupfilename, "r" )
755 for cmdline in startupfile:
756 debugOut( "processing startup line '%s'" % cmdline )
757 if not cmdline:
758 continue
759 if "|" in cmdline:
760 print("ERROR: '|' in startup file is not allowed. Ignoring line")
761 continue
762 self.commandQ.put( cmdline.strip() )
763
764 def main( self ):
765 """The main command loop"""
766 while not leave_mainloop:
767 try:
768 if self.commandQ.empty():
769 sys.stdout = self.myout.delegate
770 cmdline = raw_input( "BB>> " )
771 sys.stdout = self.myout
772 else:
773 cmdline = self.commandQ.get()
774 if cmdline:
775 allCommands = cmdline.split( ';' )
776 for command in allCommands:
777 pipecmd = None
778 #
779 # special case for expert mode
780 if command == 'python':
781 sys.stdout = self.myout.delegate
782 self.processCommand( command, "" )
783 sys.stdout = self.myout
784 else:
785 self.myout.startCommand( command )
786 if '|' in command: # disable output
787 command, pipecmd = command.split( '|' )
788 delegate = self.myout.delegate
789 self.myout.delegate = None
790 tokens = shlex.split( command, True )
791 self.processCommand( tokens[0], tokens[1:] or "" )
792 self.myout.endCommand()
793 if pipecmd is not None: # restore output
794 self.myout.delegate = delegate
795
796 pipe = popen2.Popen4( pipecmd )
797 pipe.tochild.write( "\n".join( self.myout.lastBuffer() ) )
798 pipe.tochild.close()
799 sys.stdout.write( pipe.fromchild.read() )
800 #
801 except EOFError:
802 print()
803 return
804 except KeyboardInterrupt:
805 print()
806
807##########################################################################
808# Start function - called from the BitBake command line utility
809##########################################################################
810
811def start( aCooker ):
812 global cooker
813 cooker = aCooker
814 bbshell = BitBakeShell()
815 bbshell.processStartupFile()
816 bbshell.main()
817 bbshell.cleanup()
818
819if __name__ == "__main__":
820 print("SHELL: Sorry, this program should only be called by BitBake.")