use cooker.findBestProvider() for the 'which' command
[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     * frontend for bb file manipulation?
31     * pipe output of commands into a shell command (i.e grep or sort)?
32     * job control, i.e. bring commands into background with '&', fg, bg, etc.?
33     * start parsing in background right after startup?
34     * use ; to supply more than one commnd per line
35     * command aliases / shortcuts?
36
37     * capture bb exceptions occuring during task execution
38 """
39
40 ##########################################################################
41 # Import and setup global variables
42 ##########################################################################
43
44 try:
45     set
46 except NameError:
47     from sets import Set as set
48 import sys, os, imp, readline
49 imp.load_source( "bitbake", os.path.dirname( sys.argv[0] )+"/bitbake" )
50 from bb import make, data, parse, fatal
51
52 __version__ = "0.3.5"
53 __credits__ = """BitBake Shell Version %s (C) 2005 Michael 'Mickey' Lauer <mickey@Vanille.de>
54 Type 'help' for more information, press CTRL-D to exit.""" % __version__
55
56 cmds = {}
57 leave_mainloop = False
58 cooker = None
59 parsed = False
60 debug = os.environ.get( "BBSHELL_DEBUG", "" ) != ""
61 history_file = "%s/.bbsh_history" % os.environ.get( "HOME" )
62
63 ##########################################################################
64 # Commands
65 ##########################################################################
66
67 def buildCommand( params, cmd = "build" ):
68     """Build a providee"""
69     name = params[0]
70
71     oldcmd = make.options.cmd
72     make.options.cmd = cmd
73     cooker.build_cache = []
74     cooker.build_cache_fail = []
75
76     if not parsed:
77         print "SHELL: 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."
78         parseCommand( None )
79     cooker.buildProvider( name )
80
81     make.options.cmd = oldcmd
82
83 def cleanCommand( params ):
84     """Clean a providee"""
85     buildCommand( params, "clean" )
86
87 def editCommand( params ):
88     """Call $EDITOR on a .bb file"""
89     name = params[0]
90     os.system( "%s %s" % ( os.environ.get( "EDITOR" ), completeFilePath( name ) ) )
91
92 def environmentCommand( params ):
93     """Dump out the outer BitBake environment (see bbread)"""
94     data.emit_env(sys.__stdout__, make.cfg, True)
95
96 def execCommand( params ):
97     """Execute one line of python code"""
98     exec " ".join( params ) in locals(), globals()
99
100 def exitShell( params ):
101     """Leave the BitBake Shell"""
102     if debug: print "(setting leave_mainloop to true)"
103     global leave_mainloop
104     leave_mainloop = True
105
106 def fileBuildCommand( params, cmd = "build" ):
107     """Parse and build a .bb file"""
108     name = params[0]
109     bf = completeFilePath( name )
110     print "SHELL: Calling '%s' on '%s'" % ( cmd, bf )
111
112     oldcmd = make.options.cmd
113     make.options.cmd = cmd
114     cooker.build_cache = []
115     cooker.build_cache_fail = []
116
117     try:
118         bbfile_data = parse.handle( bf, make.cfg )
119     except IOError:
120         print "SHELL: ERROR: Unable to open %s" % bf
121     else:
122         item = data.getVar('PN', bbfile_data, 1)
123         data.setVar( "_task_cache", [], bbfile_data ) # force
124         cooker.tryBuildPackage( os.path.abspath( bf ), item, bbfile_data )
125
126     make.options.cmd = oldcmd
127
128
129 def fileCleanCommand( params ):
130     """Clean a .bb file"""
131     fileBuildCommand( params, "clean" )
132
133 def fileRebuildCommand( params ):
134     """Rebuild (clean & build) a .bb file"""
135     fileCleanCommand( params )
136     fileBuildCommand( params )
137
138 def newCommand( params ):
139     """Create a new .bb file and open the editor"""
140     dirname, filename = params
141     packages = '/'.join( data.getVar( "BBFILES", make.cfg, 1 ).split('/')[:-2] )
142     fulldirname = "%s/%s" % ( packages, dirname )
143
144     if not os.path.exists( fulldirname ):
145         print "SHELL: Creating '%s'" % fulldirname
146         os.mkdir( fulldirname )
147     if os.path.exists( fulldirname ) and os.path.isdir( fulldirname ):
148         if os.path.exists( "%s/%s" % ( fulldirname, filename ) ):
149             print "SHELL: ERROR: %s/%s already exists" % ( fulldirname, filename )
150             return False
151         print "SHELL: Creating '%s/%s'" % ( fulldirname, filename )
152         newpackage = open( "%s/%s" % ( fulldirname, filename ), "w" )
153         print >>newpackage,"""DESCRIPTION = ""
154 SECTION = ""
155 AUTHOR = ""
156 HOMEPAGE = ""
157 MAINTAINER = ""
158 LICENSE = "GPL"
159
160 SRC_URI = ""
161
162 inherit base
163
164 #do_compile() {
165 #
166 #}
167
168 #do_configure() {
169 #
170 #}
171
172 #do_stage() {
173 #
174 #}
175
176 #do_install() {
177 #
178 #}
179 """
180         newpackage.close()
181         os.system( "%s %s/%s" % ( os.environ.get( "EDITOR" ), fulldirname, filename ) )
182
183 def parseCommand( params ):
184     """(Re-)parse .bb files and calculate the dependency graph"""
185     cooker.status = cooker.ParsingStatus()
186     ignore = data.getVar("ASSUME_PROVIDED", make.cfg, 1) or ""
187     cooker.status.ignored_dependencies = set( ignore.split() )
188     cooker.handleCollections( data.getVar("BBFILE_COLLECTIONS", make.cfg, 1) )
189
190     make.collect_bbfiles( cooker.myProgressCallback )
191     cooker.buildDepgraph()
192     global parsed
193     parsed = True
194     print
195
196 def printCommand( params ):
197     """Print the contents of an outer BitBake environment variable"""
198     var = params[0]
199     value = data.getVar( var, make.cfg, 1 )
200     print value
201
202 def pythonCommand( params ):
203     """Enter the expert mode - an interactive BitBake Python Interpreter"""
204     sys.ps1 = "EXPERT BB>>> "
205     sys.ps2 = "EXPERT BB... "
206     import code
207     python = code.InteractiveConsole( dict( globals() ) )
208     python.interact( "SHELL: Expert Mode - BitBake Python %s\nType 'help' for more information, press CTRL-D to switch back to BBSHELL." % sys.version )
209
210 def setVarCommand( params ):
211     """Set an outer BitBake environment variable"""
212     var, value = params
213     data.setVar( var, value, make.cfg )
214     print "OK"
215
216 def rebuildCommand( params ):
217     """Clean and rebuild a .bb file or a providee"""
218     buildCommand( params, "clean" )
219     buildCommand( params, "build" )
220
221 def statusCommand( params ):
222     print "-" * 78
223     print "build cache = '%s'" % cooker.build_cache
224     print "build cache fail = '%s'" % cooker.build_cache_fail
225     print "building list = '%s'" % cooker.building_list
226     print "build path = '%s'" % cooker.build_path
227     print "consider_msgs_cache = '%s'" % cooker.consider_msgs_cache
228     print "build stats = '%s'" % cooker.stats
229
230 def testCommand( params ):
231     """Just for testing..."""
232     print "testCommand called with '%s'" % params
233
234 def whichCommand( params ):
235     """Computes the providers for a given providee"""
236     item = params[0]
237
238     if not parsed:
239         print "SHELL: 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."
240         parseCommand( None )
241
242     preferred = data.getVar( "PREFERRED_PROVIDER_%s" % item, make.cfg, 1 )
243     if not preferred: preferred = item
244
245     try:
246         lv, lf, pv, pf = cooker.findBestProvider( preferred )
247     except KeyError:
248         lv, lf, pv, pf = (None,)*4
249
250     try:
251         providers = cooker.status.providers[item]
252     except KeyError:
253         print "SHELL: ERROR: Nothing provides", preferred
254     else:
255         for provider in providers:
256             if provider == pf: provider = " (***) %s" % provider
257             else:              provider = "       %s" % provider
258             print provider
259
260 ##########################################################################
261 # Common helper functions
262 ##########################################################################
263
264 def completeFilePath( bbfile ):
265     if not make.pkgdata: return bbfile
266     for key in make.pkgdata.keys():
267         if key.endswith( bbfile ):
268             return key
269     return bbfile
270
271 ##########################################################################
272 # Startup / Shutdown
273 ##########################################################################
274
275 def init():
276     """Register commands and set up readline"""
277     registerCommand( "help", showHelp )
278     registerCommand( "exit", exitShell )
279
280     registerCommand( "build", buildCommand, 1, "build <providee>" )
281     registerCommand( "clean", cleanCommand, 1, "clean <providee>" )
282     registerCommand( "edit", editCommand, 1, "edit <bbfile>" )
283     registerCommand( "environment", environmentCommand )
284     registerCommand( "exec", execCommand, 1, "exec <one line of pythoncode>" )
285     registerCommand( "filebuild", fileBuildCommand, 1, "filebuild <bbfile>" )
286     registerCommand( "fileclean", fileCleanCommand, 1, "fileclean <bbfile>" )
287     registerCommand( "filerebuild", fileRebuildCommand, 1, "filerebuild <bbfile>" )
288     registerCommand( "new", newCommand, 2, "new <directory> <bbfile>" )
289     registerCommand( "parse", parseCommand )
290     registerCommand( "print", printCommand, 1, "print <variable>" )
291     registerCommand( "python", pythonCommand )
292     registerCommand( "rebuild", rebuildCommand, 1, "rebuild <providee>" )
293     registerCommand( "set", setVarCommand, 2, "set <variable> <value>" )
294     registerCommand( "status", statusCommand )
295     registerCommand( "test", testCommand )
296     registerCommand( "which", whichCommand, 1, "which <providee>" )
297
298     readline.set_completer( completer )
299     readline.set_completer_delims( " " )
300     readline.parse_and_bind("tab: complete")
301
302     try:
303         global history_file
304         readline.read_history_file( history_file )
305     except IOError:
306         pass  # It doesn't exist yet.
307
308 def cleanup():
309     """Write readline history and clean up resources"""
310     if debug: print "(writing command history)"
311     try:
312         global history_file
313         readline.write_history_file( history_file )
314     except:
315         print "SHELL: Unable to save command history"
316
317 def completer( text, state ):
318     """Return a possible readline completion"""
319     if debug: print "(completer called with text='%s', state='%d'" % ( text, state )
320
321     if state == 0:
322         line = readline.get_line_buffer()
323         if " " in line:
324             line = line.split()
325             # we are in second (or more) argument
326             if line[0] == "print" or line[0] == "set":
327                 allmatches = make.cfg.keys()
328             elif line[0].startswith( "file" ):
329                 if make.pkgdata is None: allmatches = [ "(No Matches Available. Parsed yet?)" ]
330                 else: allmatches = [ x.split("/")[-1] for x in make.pkgdata.keys() ]
331             elif line[0] == "build" or line[0] == "clean" or line[0] == "which":
332                 if make.pkgdata is None: allmatches = [ "(No Matches Available. Parsed yet?)" ]
333                 else: allmatches = cooker.status.providers.iterkeys()
334             else: allmatches = [ "(No tab completion available for this command)" ]
335         else:
336             # we are in first argument
337             allmatches = cmds.iterkeys()
338
339         completer.matches = [ x for x in allmatches if x[:len(text)] == text ]
340         #print "completer.matches = '%s'" % completer.matches
341     if len( completer.matches ) > state:
342         return completer.matches[state]
343     else:
344         return None
345
346 def showCredits():
347     """Show credits (sic!)"""
348     print __credits__
349
350 def showHelp( *args ):
351     """Show a comprehensive list of commands and their purpose"""
352     print "="*35, "Available Commands", "="*35
353     allcmds = cmds.keys()
354     allcmds.sort()
355     for cmd in allcmds:
356         function,numparams,usage,helptext = cmds[cmd]
357         print "| %s | %s" % (usage.ljust(35), helptext)
358     print "="*88
359
360 def registerCommand( command, function, numparams = 0, usage = "", helptext = "" ):
361     """Register a command"""
362     if usage == "": usage = command
363     if helptext == "": helptext = function.__doc__ or "<not yet documented>"
364     cmds[command] = ( function, numparams, usage, helptext )
365
366 def processCommand( command, params ):
367     """Process a command. Check number of params and print a usage string, if appropriate"""
368     if debug: print "(processing command '%s'...)" % command
369     try:
370         function, numparams, usage, helptext = cmds[command]
371     except KeyError:
372         print "SHELL: ERROR: '%s' command is not a valid command." % command
373     else:
374         if not len( params ) == numparams:
375             print "Usage: '%s'" % usage
376             return
377         result = function( params )
378         if debug: print "(result was '%s')" % result
379
380 def main():
381     """The main command loop"""
382     while not leave_mainloop:
383         try:
384             cmdline = raw_input( "BB>> " )
385             if cmdline:
386                 commands = cmdline.split( ';' )
387                 for command in commands:
388                     if ' ' in command:
389                         processCommand( command.split()[0], command.split()[1:] )
390                     else:
391                         processCommand( command, "" )
392         except EOFError:
393             print
394             return
395         except KeyboardInterrupt:
396             print
397
398 def start( aCooker ):
399     global cooker
400     cooker = aCooker
401     showCredits()
402     init()
403     main()
404     cleanup()
405
406 if __name__ == "__main__":
407     print "SHELL: Sorry, this program should only be called by BitBake."