2 # ex:ts=4:sw=4:sts=4:et
3 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4 ##########################################################################
6 # Copyright (C) 2005 Michael 'Mickey' Lauer <mickey@Vanille.de>, Vanille Media
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.
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.
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.
20 ##########################################################################
27 * list defined tasks per package
30 * command to reparse just one (or more) bbfile(s)
31 * automatic check if reparsing is necessary (inotify?)
32 * frontend for bb file manipulation
33 * shell like output control, i.e. pipe output into grep, sort, etc.
34 * shell like job control, i.e. bring commands into background with '&', fg, bg, etc.
35 * start parsing in background right after startup
36 * print variable from package data
37 * command aliases / shortcuts
39 * clean up edit/fileedit
43 ##########################################################################
44 # Import and setup global variables
45 ##########################################################################
50 from sets import Set as set
51 import sys, os, imp, readline, socket, httplib, urllib, commands, popen2
52 imp.load_source( "bitbake", os.path.dirname( sys.argv[0] )+"/bitbake" )
53 from bb import data, parse, build, make, fatal
56 __credits__ = """BitBake Shell Version %s (C) 2005 Michael 'Mickey' Lauer <mickey@Vanille.de>
57 Type 'help' for more information, press CTRL-D to exit.""" % __version__
60 leave_mainloop = False
64 debug = os.environ.get( "BBSHELL_DEBUG", "" )
65 history_file = "%s/.bbsh_history" % os.environ.get( "HOME" )
67 ##########################################################################
68 # Class BitBakeShellCommands
69 ##########################################################################
71 class BitBakeShellCommands:
72 """This class contains the valid commands for the shell"""
74 def __init__( self, shell ):
75 """Register all the commands"""
77 for attr in BitBakeShellCommands.__dict__:
78 if not attr.startswith( "_" ):
79 if attr.endswith( "_" ):
80 command = attr[:-1].lower()
82 command = attr[:].lower()
83 method = getattr( BitBakeShellCommands, attr )
84 debugOut( "registering command '%s'" % command )
85 # scan number of arguments
86 usage = getattr( method, "usage", "" )
88 numArgs = len( usage.split() )
91 shell.registerCommand( command, method, numArgs, "%s %s" % ( command, usage ), method.__doc__ )
93 def _checkParsed( self ):
95 print "SHELL: This command needs to parse bbfiles..."
98 def buffer( self, params ):
99 """Dump specified output buffer"""
101 print self._shell.myout.buffer( int( index ) )
102 buffer.usage = "<index>"
104 def buffers( self, params ):
105 """Show the available output buffers"""
106 commands = self._shell.myout.bufferedCommands()
108 print "SHELL: No buffered commands available yet. Start doing something."
110 print "="*35, "Available Output Buffers", "="*27
111 for index, cmd in enumerate( commands ):
112 print "| %s %s" % ( str( index ).ljust( 3 ), cmd )
115 def build( self, params, cmd = "build" ):
116 """Build a providee"""
119 oldcmd = make.options.cmd
120 make.options.cmd = cmd
121 cooker.build_cache = []
122 cooker.build_cache_fail = []
127 cooker.buildProvider( name )
128 except build.EventException, e:
129 print "ERROR: Couldn't build '%s'" % name
130 global last_exception
133 make.options.cmd = oldcmd
134 build.usage = "<providee>"
136 def clean( self, params ):
137 """Clean a providee"""
138 self.build( params, "clean" )
139 clean.usage = "<providee>"
141 def compile( self, params ):
142 """Execute 'compile' on a providee"""
143 self.build( params, "compile" )
144 compile.usage = "<providee>"
146 def configure( self, params ):
147 """Execute 'configure' on a providee"""
148 self.build( params, "configure" )
149 configure.usage = "<providee>"
151 def edit( self, params ):
152 """Call $EDITOR on a .bb file"""
154 os.system( "%s %s" % ( os.environ.get( "EDITOR", "vi" ), completeFilePath( name ) ) )
155 edit.usage = "<bbfile>"
157 def environment( self, params ):
158 """Dump out the outer BitBake environment (see bbread)"""
159 data.emit_env(sys.__stdout__, make.cfg, True)
161 def exit_( self, params ):
162 """Leave the BitBake Shell"""
163 debugOut( "setting leave_mainloop to true" )
164 global leave_mainloop
165 leave_mainloop = True
167 def fetch( self, params ):
168 """Fetch a providee"""
169 self.build( params, "fetch" )
170 fetch.usage = "<providee>"
172 def fileBuild( self, params, cmd = "build" ):
173 """Parse and build a .bb file"""
175 bf = completeFilePath( name )
176 print "SHELL: Calling '%s' on '%s'" % ( cmd, bf )
178 oldcmd = make.options.cmd
179 make.options.cmd = cmd
180 cooker.build_cache = []
181 cooker.build_cache_fail = []
184 bbfile_data = parse.handle( bf, make.cfg )
185 except parse.ParseError:
186 print "ERROR: Unable to open or parse '%s'" % bf
188 item = data.getVar('PN', bbfile_data, 1)
189 data.setVar( "_task_cache", [], bbfile_data ) # force
191 cooker.tryBuildPackage( os.path.abspath( bf ), item, bbfile_data )
192 except build.EventException, e:
193 print "ERROR: Couldn't build '%s'" % name
194 global last_exception
197 make.options.cmd = oldcmd
198 fileBuild.usage = "<bbfile>"
200 def fileClean( self, params ):
201 """Clean a .bb file"""
202 self.fileBuild( params, "clean" )
203 fileClean.usage = "<bbfile>"
205 def fileRebuild( self, params ):
206 """Rebuild (clean & build) a .bb file"""
207 self.fileClean( params )
208 self.fileBuild( params )
209 fileRebuild.usage = "<bbfile>"
211 def force( self, params ):
212 """Toggle force task execution flag (see bitbake -f)"""
213 make.options.force = not make.options.force
214 print "SHELL: Force Flag is now '%s'" % repr( make.options.force )
216 def help( self, params ):
217 """Show a comprehensive list of commands and their purpose"""
218 print "="*30, "Available Commands", "="*30
219 allcmds = cmds.keys()
222 function,numparams,usage,helptext = cmds[cmd]
223 print "| %s | %s" % (usage.ljust(30), helptext)
226 def lastError( self, params ):
227 """Show the reason or log that was produced by the last BitBake event exception"""
228 if last_exception is None:
229 print "SHELL: No Errors yet (Phew)..."
231 reason, event = last_exception.args
232 print "SHELL: Reason for the last error: '%s'" % reason
234 msg, filename = reason.split( ':' )
235 filename = filename.strip()
236 print "SHELL: Dumping log file for last error:"
238 print open( filename ).read()
240 print "ERROR: Couldn't open '%s'" % filename
242 def new( self, params ):
243 """Create a new .bb file and open the editor"""
244 dirname, filename = params
245 packages = '/'.join( data.getVar( "BBFILES", make.cfg, 1 ).split('/')[:-2] )
246 fulldirname = "%s/%s" % ( packages, dirname )
248 if not os.path.exists( fulldirname ):
249 print "SHELL: Creating '%s'" % fulldirname
250 os.mkdir( fulldirname )
251 if os.path.exists( fulldirname ) and os.path.isdir( fulldirname ):
252 if os.path.exists( "%s/%s" % ( fulldirname, filename ) ):
253 print "SHELL: ERROR: %s/%s already exists" % ( fulldirname, filename )
255 print "SHELL: Creating '%s/%s'" % ( fulldirname, filename )
256 newpackage = open( "%s/%s" % ( fulldirname, filename ), "w" )
257 print >>newpackage,"""DESCRIPTION = ""
286 os.system( "%s %s/%s" % ( os.environ.get( "EDITOR" ), fulldirname, filename ) )
287 new.usage = "<directory> <filename>"
289 def pasteBin( self, params ):
290 """Send a command + output buffer to http://pastebin.com"""
292 contents = self._shell.myout.buffer( int( index ) )
293 status, error, location = sendToPastebin( contents )
295 print "SHELL: Pasted to %s" % location
297 print "ERROR: %s %s" % ( response.status, response.reason )
298 pasteBin.usage = "<index>"
300 def pasteLog( self, params ):
301 """Send the last event exception error log (if there is one) to http://pastebin.com"""
302 if last_exception is None:
303 print "SHELL: No Errors yet (Phew)..."
305 reason, event = last_exception.args
306 print "SHELL: Reason for the last error: '%s'" % reason
308 msg, filename = reason.split( ':' )
309 filename = filename.strip()
310 print "SHELL: Pasting log file to pastebin..."
312 status, error, location = sendToPastebin( open( filename ).read() )
315 print "SHELL: Pasted to %s" % location
317 print "ERROR: %s %s" % ( response.status, response.reason )
319 def patch( self, params ):
320 """Execute 'patch' command on a providee"""
321 self.build( params, "patch" )
322 patch.usage = "<providee>"
324 def parse( self, params ):
325 """(Re-)parse .bb files and calculate the dependency graph"""
326 cooker.status = cooker.ParsingStatus()
327 ignore = data.getVar("ASSUME_PROVIDED", make.cfg, 1) or ""
328 cooker.status.ignored_dependencies = set( ignore.split() )
329 cooker.handleCollections( data.getVar("BBFILE_COLLECTIONS", make.cfg, 1) )
331 make.collect_bbfiles( cooker.myProgressCallback )
332 cooker.buildDepgraph()
337 def getvar( self, params ):
338 """Dump the contents of an outer BitBake environment variable"""
340 value = data.getVar( var, make.cfg, 1 )
342 getvar.usage = "<variable>"
344 def print_( self, params ):
345 """Dump all files or providers"""
349 for i in make.pkgdata.keys(): print i
350 elif what == "providers":
352 for i in cooker.status.providers.keys(): print i
354 print "Usage: print %s" % self.print_.usage
355 print_.usage = "<files|providers>"
357 def python( self, params ):
358 """Enter the expert mode - an interactive BitBake Python Interpreter"""
359 sys.ps1 = "EXPERT BB>>> "
360 sys.ps2 = "EXPERT BB... "
362 interpreter = code.InteractiveConsole( dict( globals() ) )
363 interpreter.interact( "SHELL: Expert Mode - BitBake Python %s\nType 'help' for more information, press CTRL-D to switch back to BBSHELL." % sys.version )
365 def setVar( self, params ):
366 """Set an outer BitBake environment variable"""
368 data.setVar( var, value, make.cfg )
370 setVar.usage = "<variable> <value>"
372 def rebuild( self, params ):
373 """Clean and rebuild a .bb file or a providee"""
374 self.build( params, "clean" )
375 self.build( params, "build" )
376 rebuild.usage = "<providee>"
378 def shell( self, params ):
379 """Execute a shell command and dump the output"""
381 print commands.getoutput( " ".join( params ) )
382 shell.usage = "<...>"
384 def stage( self, params ):
385 """Execute 'stage' on a providee"""
386 self.build( params, "stage" )
387 stage.usage = "<providee>"
389 def status( self, params ):
390 """<just for testing>"""
392 print "build cache = '%s'" % cooker.build_cache
393 print "build cache fail = '%s'" % cooker.build_cache_fail
394 print "building list = '%s'" % cooker.building_list
395 print "build path = '%s'" % cooker.build_path
396 print "consider_msgs_cache = '%s'" % cooker.consider_msgs_cache
397 print "build stats = '%s'" % cooker.stats
398 if last_exception is not None: print "last_exception = '%s'" % repr( last_exception.args )
399 print "memory output contents = '%s'" % self._shell.myout._buffer
401 def test( self, params ):
402 """<just for testing>"""
403 print "testCommand called with '%s'" % params
405 def unpack( self, params ):
406 """Execute 'unpack' on a providee"""
407 self.build( params, "unpack" )
408 unpack.usage = "<providee>"
410 def which( self, params ):
411 """Computes the providers for a given providee"""
416 preferred = data.getVar( "PREFERRED_PROVIDER_%s" % item, make.cfg, 1 )
417 if not preferred: preferred = item
420 lv, lf, pv, pf = cooker.findBestProvider( preferred )
422 lv, lf, pv, pf = (None,)*4
425 providers = cooker.status.providers[item]
427 print "SHELL: ERROR: Nothing provides", preferred
429 for provider in providers:
430 if provider == pf: provider = " (***) %s" % provider
431 else: provider = " %s" % provider
433 which.usage = "<providee>"
435 ##########################################################################
436 # Common helper functions
437 ##########################################################################
439 def completeFilePath( bbfile ):
440 """Get the complete bbfile path"""
441 if not make.pkgdata: return bbfile
442 for key in make.pkgdata.keys():
443 if key.endswith( bbfile ):
447 def sendToPastebin( content ):
448 """Send content to http://www.pastebin.com"""
450 mydata["parent_pid"] = ""
451 mydata["format"] = "bash"
452 mydata["code2"] = content
453 mydata["paste"] = "Send"
454 mydata["poster"] = "%s@%s" % ( os.environ.get( "USER", "unknown" ), socket.gethostname() or "unknown" )
455 params = urllib.urlencode( mydata )
456 headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain"}
458 conn = httplib.HTTPConnection( "pastebin.com:80" )
459 conn.request("POST", "/", params, headers )
461 response = conn.getresponse()
464 return response.status, response.reason, response.getheader( "location" ) or "unknown"
466 def completer( text, state ):
467 """Return a possible readline completion"""
468 debugOut( "completer called with text='%s', state='%d'" % ( text, state ) )
471 line = readline.get_line_buffer()
474 # we are in second (or more) argument
475 if line[0] in cmds and hasattr( cmds[line[0]][0], "usage" ): # known command and usage
476 u = getattr( cmds[line[0]][0], "usage" ).split()[0]
477 if u == "<variable>":
478 allmatches = make.cfg.keys()
479 elif u == "<bbfile>":
480 if make.pkgdata is None: allmatches = [ "(No Matches Available. Parsed yet?)" ]
481 else: allmatches = [ x.split("/")[-1] for x in make.pkgdata.keys() ]
482 elif u == "<providee>":
483 if make.pkgdata is None: allmatches = [ "(No Matches Available. Parsed yet?)" ]
484 else: allmatches = cooker.status.providers.iterkeys()
485 else: allmatches = [ "(No tab completion available for this command)" ]
486 else: allmatches = [ "(No tab completion available for this command)" ]
488 # we are in first argument
489 allmatches = cmds.iterkeys()
491 completer.matches = [ x for x in allmatches if x[:len(text)] == text ]
492 #print "completer.matches = '%s'" % completer.matches
493 if len( completer.matches ) > state:
494 return completer.matches[state]
498 def debugOut( text ):
500 sys.stderr.write( "( %s )\n" % text )
502 ##########################################################################
504 ##########################################################################
507 """File-like output class buffering the output of the last 10 commands"""
508 def __init__( self, delegate ):
509 self.delegate = delegate
514 def startCommand( self, command ):
515 self._command = command
517 def endCommand( self ):
518 if self._command is not None:
519 if len( self._buffer ) == 10: del self._buffer[0]
520 self._buffer.append( ( self._command, self.text ) )
521 def removeLast( self ):
523 del self._buffer[ len( self._buffer ) - 1 ]
526 def lastBuffer( self ):
528 return self._buffer[ len( self._buffer ) -1 ][1]
529 def bufferedCommands( self ):
530 return [ cmd for cmd, output in self._buffer ]
531 def buffer( self, i ):
532 if i < len( self._buffer ):
533 return "BB>> %s\n%s" % ( self._buffer[i][0], "".join( self._buffer[i][1] ) )
534 else: return "ERROR: Invalid buffer number. Buffer needs to be in (0, %d)" % ( len( self._buffer ) - 1 )
535 def write( self, text ):
536 if self._command is not None and text != "BB>> ": self.text.append( text )
537 if self.delegate is not None: self.delegate.write( text )
539 return self.delegate.flush()
541 return self.delegate.fileno()
543 return self.delegate.isatty()
545 ##########################################################################
547 ##########################################################################
551 def __init__( self ):
552 """Register commands and set up readline"""
553 self.commands = BitBakeShellCommands( self )
554 self.myout = MemoryOutput( sys.stdout )
556 readline.set_completer( completer )
557 readline.set_completer_delims( " " )
558 readline.parse_and_bind("tab: complete")
562 readline.read_history_file( history_file )
564 pass # It doesn't exist yet.
569 """Write readline history and clean up resources"""
570 debugOut( "writing command history" )
573 readline.write_history_file( history_file )
575 print "SHELL: Unable to save command history"
577 def registerCommand( self, command, function, numparams = 0, usage = "", helptext = "" ):
578 """Register a command"""
579 if usage == "": usage = command
580 if helptext == "": helptext = function.__doc__ or "<not yet documented>"
581 cmds[command] = ( function, numparams, usage, helptext )
583 def processCommand( self, command, params ):
584 """Process a command. Check number of params and print a usage string, if appropriate"""
585 debugOut( "processing command '%s'..." % command )
587 function, numparams, usage, helptext = cmds[command]
589 print "SHELL: ERROR: '%s' command is not a valid command." % command
590 self.myout.removeLast()
592 if (numparams != -1) and (not len( params ) == numparams):
593 print "Usage: '%s'" % usage
596 result = function( self.commands, params )
597 debugOut( "result was '%s'" % result )
600 """The main command loop"""
601 while not leave_mainloop:
603 sys.stdout = self.myout.delegate
604 cmdline = raw_input( "BB>> " )
605 sys.stdout = self.myout
607 allCommands = cmdline.split( ';' )
608 for command in allCommands:
611 self.myout.startCommand( command )
612 if '|' in command: # disable output
613 command, pipecmd = command.split( '|' )
614 delegate = self.myout.delegate
615 self.myout.delegate = None
617 self.processCommand( command.split()[0], command.split()[1:] )
619 self.processCommand( command, "" )
620 self.myout.endCommand()
621 if pipecmd is not None: # restore output
622 self.myout.delegate = delegate
624 pipe = popen2.Popen4( pipecmd )
625 pipe.tochild.write( "\n".join( self.myout.lastBuffer() ) )
627 sys.stdout.write( pipe.fromchild.read() )
632 except KeyboardInterrupt:
635 ##########################################################################
636 # Start function - called from the BitBake command line utility
637 ##########################################################################
639 def start( aCooker ):
642 bbshell = BitBakeShell()
646 if __name__ == "__main__":
647 print "SHELL: Sorry, this program should only be called by BitBake."