#!/usr/bin/env python # ex:ts=4:sw=4:sts=4:et # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- ########################################################################## # # Copyright (C) 2005 Michael 'Mickey' Lauer , Vanille Media # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., 59 Temple # Place, Suite 330, Boston, MA 02111-1307 USA. # ########################################################################## """ BitBake Shell TODO: * specify tasks * specify force * command to reparse just one (or more) bbfile(s) * automatic check if reparsing is necessary (inotify?) * bb file wizard * capture bb exceptions occuring during task execution """ ########################################################################## # Import and setup global variables ########################################################################## try: set except NameError: from sets import Set as set import sys, os, imp, readline imp.load_source( "bitbake", os.path.dirname( sys.argv[0] )+"/bitbake" ) from bb import make, data, parse, fatal __version__ = 0.3 __credits__ = """BitBake Shell Version %2.1f (C) 2005 Michael 'Mickey' Lauer Type 'help' for more information, press CTRL-D to exit.""" % __version__ cmds = {} leave_mainloop = False cooker = None parsed = False debug = os.environ.get( "BBSHELL_DEBUG", "" ) != "" history_file = "%s/.bbsh_history" % os.environ.get( "HOME" ) ########################################################################## # Commands ########################################################################## def buildCommand( params, cmd = "build" ): """Build a providee""" name = params[0] oldcmd = make.options.cmd make.options.cmd = cmd cooker.build_cache = [] cooker.build_cache_fail = [] if not parsed: print "BBSHELL: D'oh! The .bb files haven't been parsed yet. Next time call 'parse' before building stuff. This time I'll do it for 'ya." parseCommand( None ) cooker.buildProvider( name ) make.options.cmd = oldcmd def cleanCommand( params ): """Clean a providee""" buildCommand( params, "clean" ) def editCommand( params ): """Call $EDITOR on a .bb file""" name = params[0] os.system( "%s %s" % ( os.environ.get( "EDITOR" ), completeFilePath( name ) ) ) def environmentCommand( params ): """Dump out the outer BitBake environment (see bbread)""" data.emit_env(sys.__stdout__, make.cfg, True) def execCommand( params ): """Execute one line of python code""" exec " ".join( params ) in locals(), globals() def exitShell( params ): """Leave the BitBake Shell""" if debug: print "(setting leave_mainloop to true)" global leave_mainloop leave_mainloop = True def fileBuildCommand( params, cmd = "build" ): """Parse and build a .bb file""" name = params[0] bf = completeFilePath( name ) print "Calling '%s' on '%s'" % ( cmd, bf ) oldcmd = make.options.cmd make.options.cmd = cmd cooker.build_cache = [] cooker.build_cache_fail = [] try: bbfile_data = parse.handle( bf, make.cfg ) except IOError: print "ERROR: Unable to open %s" % bf else: item = data.getVar('PN', bbfile_data, 1) data.setVar( "_task_cache", [], bbfile_data ) # force cooker.tryBuildPackage( os.path.abspath( bf ), item, bbfile_data ) make.options.cmd = oldcmd def fileCleanCommand( params ): """Clean a .bb file""" fileBuildCommand( params, "clean" ) def fileRebuildCommand( params ): """Rebuild (clean & build) a .bb file""" fileCleanCommand( params ) fileBuildCommand( params ) def parseCommand( params ): """(Re-)parse .bb files and calculate the dependency graph""" cooker.status = cooker.ParsingStatus() ignore = data.getVar("ASSUME_PROVIDED", make.cfg, 1) or "" cooker.status.ignored_dependencies = set( ignore.split() ) cooker.handleCollections( data.getVar("BBFILE_COLLECTIONS", make.cfg, 1) ) make.collect_bbfiles( cooker.myProgressCallback ) cooker.buildDepgraph() global parsed parsed = True print def printCommand( params ): """Print the contents of an outer BitBake environment variable""" var = params[0] value = data.getVar( var, make.cfg, 1 ) print value def pythonCommand( params ): """Enter the expert mode - an interactive BitBake Python Interpreter""" sys.ps1 = "EXPERT BB>>> " sys.ps2 = "EXPERT BB... " import code python = code.InteractiveConsole( dict( globals() ) ) python.interact( "BBSHELL: Expert Mode - BitBake Python %s\nType 'help' for more information, press CTRL-D to switch back to BBSHELL." % sys.version ) def setVarCommand( params ): """Set an outer BitBake environment variable""" var, value = params data.setVar( var, value, make.cfg ) print "OK" def rebuildCommand( params ): """Clean and rebuild a .bb file or a providee""" buildCommand( params, "clean" ) buildCommand( params, "build" ) def statusCommand( params ): print "-" * 78 print "build cache = '%s'" % cooker.build_cache print "build cache fail = '%s'" % cooker.build_cache_fail print "building list = '%s'" % cooker.building_list print "build path = '%s'" % cooker.build_path print "consider_msgs_cache = '%s'" % cooker.consider_msgs_cache print "build stats = '%s'" % cooker.stats def testCommand( params ): """Just for testing...""" print "testCommand called with '%s'" % params def whichCommand( params ): """Computes the providers for a given providee""" item = params[0] if not parsed: print "BBSHELL: D'oh! The .bb files haven't been parsed yet. Next time call 'parse' before building stuff. This time I'll do it for 'ya." parseCommand( None ) try: providers = cooker.status.providers[item] except KeyError: print "ERROR: Nothing provides", item else: print "Providers for '%s':" % item print "------------------------------------------------------" for provider in providers: print " ", provider pv = data.getVar( "PREFERRED_VERSION_%s" % item, make.cfg, 1 ) pp = data.getVar( "PREFERRED_PROVIDER_%s" % item, make.cfg, 1 ) if pv: print "Preferred version for '%s': ", pv if pp: print "Preferred version for '%s': ", pp ########################################################################## # Common helper functions ########################################################################## def completeFilePath( bbfile ): if not make.pkgdata: return bbfile for key in make.pkgdata.keys(): if key.endswith( bbfile ): return key return bbfile ########################################################################## # Startup / Shutdown ########################################################################## def init(): """Register commands and set up readline""" registerCommand( "help", showHelp ) registerCommand( "exit", exitShell ) registerCommand( "build", buildCommand, 1, "build " ) registerCommand( "clean", cleanCommand, 1, "clean " ) registerCommand( "edit", editCommand, 1, "edit " ) registerCommand( "environment", environmentCommand ) registerCommand( "exec", execCommand, 1, "exec " ) registerCommand( "filebuild", fileBuildCommand, 1, "filebuild " ) registerCommand( "fileclean", fileCleanCommand, 1, "fileclean " ) registerCommand( "filerebuild", fileRebuildCommand, 1, "filerebuild " ) registerCommand( "parse", parseCommand ) registerCommand( "print", printCommand, 1, "print " ) registerCommand( "python", pythonCommand ) registerCommand( "rebuild", rebuildCommand, 1, "rebuild " ) registerCommand( "set", setVarCommand, 2, "set " ) registerCommand( "status", statusCommand ) registerCommand( "test", testCommand ) registerCommand( "which", whichCommand, 1, "which " ) readline.set_completer( completer ) readline.set_completer_delims( " " ) readline.parse_and_bind("tab: complete") try: global history_file readline.read_history_file( history_file ) except IOError: pass # It doesn't exist yet. def cleanup(): """Write readline history and clean up resources""" if debug: print "(writing command history)" try: global history_file readline.write_history_file( history_file ) except: print "BBSHELL: Unable to save command history" def completer( text, state ): """Return a possible readline completion""" if debug: print "(completer called with text='%s', state='%d'" % ( text, state ) if state == 0: line = readline.get_line_buffer() if " " in line: line = line.split() # we are in second (or more) argument if line[0] == "print" or line[0] == "set": allmatches = make.cfg.keys() elif line[0].startswith( "file" ): if make.pkgdata is None: allmatches = [ "(No Matches Available. Parsed yet?)" ] else: allmatches = [ x.split("/")[-1] for x in make.pkgdata.keys() ] elif line[0] == "build" or line[0] == "clean" or line[0] == "which": if make.pkgdata is None: allmatches = [ "(No Matches Available. Parsed yet?)" ] else: allmatches = cooker.status.providers.iterkeys() else: allmatches = [ "(No tab completion available for this command)" ] else: # we are in first argument allmatches = cmds.iterkeys() completer.matches = [ x for x in allmatches if x[:len(text)] == text ] #print "completer.matches = '%s'" % completer.matches if len( completer.matches ) > state: return completer.matches[state] else: return None def showCredits(): """Show credits (sic!)""" print __credits__ def showHelp( *args ): """Show a comprehensive list of commands and their purpose""" print "="*35, "Available Commands", "="*35 allcmds = cmds.keys() allcmds.sort() for cmd in allcmds: function,numparams,usage,helptext = cmds[cmd] print "| %s | %s" % (usage.ljust(35), helptext) print "="*88 def registerCommand( command, function, numparams = 0, usage = "", helptext = "" ): """Register a command""" if usage == "": usage = command if helptext == "": helptext = function.__doc__ or "" cmds[command] = ( function, numparams, usage, helptext ) def processCommand( command, params ): """Process a command. Check number of params and print a usage string, if appropriate""" if debug: print "(processing command '%s'...)" % command try: function, numparams, usage, helptext = cmds[command] except KeyError: print "Error: '%s' command is not a valid command." % command else: if not len( params ) == numparams: print "Usage: '%s'" % usage return result = function( params ) if debug: print "(result was '%s')" % result def main(): """The main command loop""" while not leave_mainloop: try: cmdline = raw_input( "BB>> " ) if cmdline: if ' ' in cmdline: processCommand( cmdline.split()[0], cmdline.split()[1:] ) else: processCommand( cmdline, "" ) except EOFError: print return except KeyboardInterrupt: print def start( aCooker ): global cooker cooker = aCooker showCredits() init() main() cleanup() if __name__ == "__main__": print "BBSHELL: Sorry, this program should only be called by BitBake."