- fix bug in file*** commands (need to clean the _task_cache per
[vuplus_bitbake] / lib / bb / shell.py
1 #!/usr/bin/env python
2 # ex:ts=4:sw=4:sts=4:et
3 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4 ##########################################################################
5 #
6 # Copyright (C) 2005 Michael 'Mickey' Lauer <mickey@Vanille.de>, Vanille Media
7 #
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.
11 #
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.
15 #
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.
19 #
20 ##########################################################################
21
22 """
23 BitBake Shell
24
25 TODO:
26     * specify tasks
27     * specify force
28     * command to reparse just one (or more) bbfile(s)
29     * automatic check if reparsing is necessary (inotify?)
30     * bb file wizard
31
32     * capture bb exceptions occuring during task execution
33 """
34
35 ##########################################################################
36 # Import and setup global variables
37 ##########################################################################
38
39 try:
40     set
41 except NameError:
42     from sets import Set as set
43 import sys, os, imp, readline
44 imp.load_source( "bitbake", os.path.dirname( sys.argv[0] )+"/bitbake" )
45 from bb import make, data, parse, fatal
46
47 __version__ = 0.3
48 __credits__ = """BitBake Shell Version %2.1f (C) 2005 Michael 'Mickey' Lauer <mickey@Vanille.de>
49 Type 'help' for more information, press CTRL-D to exit.""" % __version__
50
51 cmds = {}
52 leave_mainloop = False
53 cooker = None
54 parsed = False
55 debug = os.environ.get( "BBSHELL_DEBUG", "" ) != ""
56 history_file = "%s/.bbsh_history" % os.environ.get( "HOME" )
57
58 ##########################################################################
59 # Commands
60 ##########################################################################
61
62 def buildCommand( params, cmd = "build" ):
63     """Build a providee"""
64     name = params[0]
65
66     oldcmd = make.options.cmd
67     make.options.cmd = cmd
68     cooker.build_cache = []
69     cooker.build_cache_fail = []
70
71     if not parsed:
72         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."
73         parseCommand( None )
74     cooker.buildProvider( name )
75
76     make.options.cmd = oldcmd
77
78 def cleanCommand( params ):
79     """Clean a providee"""
80     buildCommand( params, "clean" )
81
82 def editCommand( params ):
83     """Call $EDITOR on a .bb file"""
84     name = params[0]
85     os.system( "%s %s" % ( os.environ.get( "EDITOR" ), completeFilePath( name ) ) )
86
87 def environmentCommand( params ):
88     """Dump out the outer BitBake environment (see bbread)"""
89     data.emit_env(sys.__stdout__, make.cfg, True)
90
91 def execCommand( params ):
92     """Execute one line of python code"""
93     exec " ".join( params ) in locals(), globals()
94
95 def exitShell( params ):
96     """Leave the BitBake Shell"""
97     if debug: print "(setting leave_mainloop to true)"
98     global leave_mainloop
99     leave_mainloop = True
100
101 def fileBuildCommand( params, cmd = "build" ):
102     """Parse and build a .bb file"""
103     name = params[0]
104     bf = completeFilePath( name )
105     print "Calling '%s' on '%s'" % ( cmd, bf )
106
107     oldcmd = make.options.cmd
108     make.options.cmd = cmd
109     cooker.build_cache = []
110     cooker.build_cache_fail = []
111
112     try:
113         bbfile_data = parse.handle( bf, make.cfg )
114     except IOError:
115         print "ERROR: Unable to open %s" % bf
116     else:
117         item = data.getVar('PN', bbfile_data, 1)
118         data.setVar( "_task_cache", [], bbfile_data ) # force
119         cooker.tryBuildPackage( os.path.abspath( bf ), item, bbfile_data )
120
121     make.options.cmd = oldcmd
122
123
124 def fileCleanCommand( params ):
125     """Clean a .bb file"""
126     fileBuildCommand( params, "clean" )
127
128 def fileRebuildCommand( params ):
129     """Rebuild (clean & build) a .bb file"""
130     fileCleanCommand( params )
131     fileBuildCommand( params )
132
133 def parseCommand( params ):
134     """(Re-)parse .bb files and calculate the dependency graph"""
135     cooker.status = cooker.ParsingStatus()
136     ignore = data.getVar("ASSUME_PROVIDED", make.cfg, 1) or ""
137     cooker.status.ignored_dependencies = set( ignore.split() )
138     cooker.handleCollections( data.getVar("BBFILE_COLLECTIONS", make.cfg, 1) )
139
140     make.collect_bbfiles( cooker.myProgressCallback )
141     cooker.buildDepgraph()
142     global parsed
143     parsed = True
144     print
145
146 def printCommand( params ):
147     """Print the contents of an outer BitBake environment variable"""
148     var = params[0]
149     value = data.getVar( var, make.cfg, 1 )
150     print value
151
152 def pythonCommand( params ):
153     """Enter the expert mode - an interactive BitBake Python Interpreter"""
154     sys.ps1 = "EXPERT BB>>> "
155     sys.ps2 = "EXPERT BB... "
156     import code
157     python = code.InteractiveConsole( dict( globals() ) )
158     python.interact( "BBSHELL: Expert Mode - BitBake Python %s\nType 'help' for more information, press CTRL-D to switch back to BBSHELL." % sys.version )
159
160 def setVarCommand( params ):
161     """Set an outer BitBake environment variable"""
162     var, value = params
163     data.setVar( var, value, make.cfg )
164     print "OK"
165
166 def rebuildCommand( params ):
167     """Clean and rebuild a .bb file or a providee"""
168     buildCommand( params, "clean" )
169     buildCommand( params, "build" )
170
171 def statusCommand( params ):
172     print "-" * 78
173     print "build cache = '%s'" % cooker.build_cache
174     print "build cache fail = '%s'" % cooker.build_cache_fail
175     print "building list = '%s'" % cooker.building_list
176     print "build path = '%s'" % cooker.build_path
177     print "consider_msgs_cache = '%s'" % cooker.consider_msgs_cache
178     print "build stats = '%s'" % cooker.stats
179
180 def testCommand( params ):
181     """Just for testing..."""
182     print "testCommand called with '%s'" % params
183
184 def whichCommand( params ):
185     """Computes the providers for a given providee"""
186     item = params[0]
187
188     if not parsed:
189         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."
190         parseCommand( None )
191     try:
192         providers = cooker.status.providers[item]
193     except KeyError:
194         print "ERROR: Nothing provides", item
195     else:
196         print "Providers for '%s':" % item
197         print "------------------------------------------------------"
198         for provider in providers:
199             print "   ", provider
200         pv = data.getVar( "PREFERRED_VERSION_%s" % item, make.cfg, 1 )
201         pp = data.getVar( "PREFERRED_PROVIDER_%s" % item, make.cfg, 1 )
202         if pv: print "Preferred version for '%s': ", pv
203         if pp: print "Preferred version for '%s': ", pp
204
205 ##########################################################################
206 # Common helper functions
207 ##########################################################################
208
209 def completeFilePath( bbfile ):
210     if not make.pkgdata: return bbfile
211     for key in make.pkgdata.keys():
212         if key.endswith( bbfile ):
213             return key
214     return bbfile
215
216 ##########################################################################
217 # Startup / Shutdown
218 ##########################################################################
219
220 def init():
221     """Register commands and set up readline"""
222     registerCommand( "help", showHelp )
223     registerCommand( "exit", exitShell )
224
225     registerCommand( "build", buildCommand, 1, "build <providee>" )
226     registerCommand( "clean", cleanCommand, 1, "clean <providee>" )
227     registerCommand( "edit", editCommand, 1, "edit <bbfile>" )
228     registerCommand( "environment", environmentCommand )
229     registerCommand( "exec", execCommand, 1, "exec <one line of pythoncode>" )
230     registerCommand( "filebuild", fileBuildCommand, 1, "filebuild <bbfile>" )
231     registerCommand( "fileclean", fileCleanCommand, 1, "fileclean <bbfile>" )
232     registerCommand( "filerebuild", fileRebuildCommand, 1, "filerebuild <bbfile>" )
233     registerCommand( "parse", parseCommand )
234     registerCommand( "print", printCommand, 1, "print <variable>" )
235     registerCommand( "python", pythonCommand )
236     registerCommand( "rebuild", rebuildCommand, 1, "rebuild <providee>" )
237     registerCommand( "set", setVarCommand, 2, "set <variable> <value>" )
238     registerCommand( "status", statusCommand )
239     registerCommand( "test", testCommand )
240     registerCommand( "which", whichCommand, 1, "which <providee>" )
241
242     readline.set_completer( completer )
243     readline.set_completer_delims( " " )
244     readline.parse_and_bind("tab: complete")
245
246     try:
247         global history_file
248         readline.read_history_file( history_file )
249     except IOError:
250         pass  # It doesn't exist yet.
251
252 def cleanup():
253     """Write readline history and clean up resources"""
254     if debug: print "(writing command history)"
255     try:
256         global history_file
257         readline.write_history_file( history_file )
258     except:
259         print "BBSHELL: Unable to save command history"
260
261 def completer( text, state ):
262     """Return a possible readline completion"""
263     if debug: print "(completer called with text='%s', state='%d'" % ( text, state )
264
265     if state == 0:
266         line = readline.get_line_buffer()
267         if " " in line:
268             line = line.split()
269             # we are in second (or more) argument
270             if line[0] == "print" or line[0] == "set":
271                 allmatches = make.cfg.keys()
272             elif line[0].startswith( "file" ):
273                 if make.pkgdata is None: allmatches = [ "(No Matches Available. Parsed yet?)" ]
274                 else: allmatches = [ x.split("/")[-1] for x in make.pkgdata.keys() ]
275             elif line[0] == "build" or line[0] == "clean" or line[0] == "which":
276                 if make.pkgdata is None: allmatches = [ "(No Matches Available. Parsed yet?)" ]
277                 else: allmatches = cooker.status.providers.iterkeys()
278             else: allmatches = [ "(No tab completion available for this command)" ]
279         else:
280             # we are in first argument
281             allmatches = cmds.iterkeys()
282
283         completer.matches = [ x for x in allmatches if x[:len(text)] == text ]
284         #print "completer.matches = '%s'" % completer.matches
285     if len( completer.matches ) > state:
286         return completer.matches[state]
287     else:
288         return None
289
290 def showCredits():
291     """Show credits (sic!)"""
292     print __credits__
293
294 def showHelp( *args ):
295     """Show a comprehensive list of commands and their purpose"""
296     print "="*35, "Available Commands", "="*35
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(35), helptext)
302     print "="*88
303
304 def registerCommand( command, function, numparams = 0, usage = "", helptext = "" ):
305     """Register a command"""
306     if usage == "": usage = command
307     if helptext == "": helptext = function.__doc__ or "<not yet documented>"
308     cmds[command] = ( function, numparams, usage, helptext )
309
310 def processCommand( command, params ):
311     """Process a command. Check number of params and print a usage string, if appropriate"""
312     if debug: print "(processing command '%s'...)" % command
313     try:
314         function, numparams, usage, helptext = cmds[command]
315     except KeyError:
316         print "Error: '%s' command is not a valid command." % command
317     else:
318         if not len( params ) == numparams:
319             print "Usage: '%s'" % usage
320             return
321         result = function( params )
322         if debug: print "(result was '%s')" % result
323
324 def main():
325     """The main command loop"""
326     while not leave_mainloop:
327         try:
328             cmdline = raw_input( "BB>> " )
329             if cmdline:
330                 if ' ' in cmdline:
331                     processCommand( cmdline.split()[0], cmdline.split()[1:] )
332                 else:
333                     processCommand( cmdline, "" )
334         except EOFError:
335             print
336             return
337         except KeyboardInterrupt:
338             print
339
340 def start( aCooker ):
341     global cooker
342     cooker = aCooker
343     showCredits()
344     init()
345     main()
346     cleanup()
347
348 if __name__ == "__main__":
349     print "BBSHELL: Sorry, this program should only be called by BitBake."