Add missing 'return False' to the SystemExit handler in runAsyncCommand
[bitbake.git] / lib / bb / command.py
1 """
2 BitBake 'Command' module
3
4 Provide an interface to interact with the bitbake server through 'commands'
5 """
6
7 # Copyright (C) 2006-2007  Richard Purdie
8 #
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License version 2 as
11 # published by the Free Software Foundation.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License along
19 # with this program; if not, write to the Free Software Foundation, Inc.,
20 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22 """
23 The bitbake server takes 'commands' from its UI/commandline.
24 Commands are either synchronous or asynchronous.
25 Async commands return data to the client in the form of events.
26 Sync commands must only return data through the function return value
27 and must not trigger events, directly or indirectly.
28 Commands are queued in a CommandQueue
29 """
30
31 import bb.event
32 import bb.cooker
33 import bb.data
34
35 async_cmds = {}
36 sync_cmds = {}
37
38 class Command:
39     """
40     A queue of asynchronous commands for bitbake
41     """
42     def __init__(self, cooker):
43
44         self.cooker = cooker
45         self.cmds_sync = CommandsSync()
46         self.cmds_async = CommandsAsync()
47
48         # FIXME Add lock for this
49         self.currentAsyncCommand = None
50
51         for attr in CommandsSync.__dict__:
52             command = attr[:].lower()
53             method = getattr(CommandsSync, attr)
54             sync_cmds[command] = (method)
55
56         for attr in CommandsAsync.__dict__:
57             command = attr[:].lower()
58             method = getattr(CommandsAsync, attr)
59             async_cmds[command] = (method)
60
61     def runCommand(self, commandline):
62         try:
63             command = commandline.pop(0)
64             if command in CommandsSync.__dict__:
65                 # Can run synchronous commands straight away
66                 return getattr(CommandsSync, command)(self.cmds_sync, self, commandline)
67             if self.currentAsyncCommand is not None:
68                 return "Busy (%s in progress)" % self.currentAsyncCommand[0]
69             if command not in CommandsAsync.__dict__:
70                 return "No such command"
71             self.currentAsyncCommand = (command, commandline)
72             self.cooker.server.register_idle_function(self.cooker.runCommands, self.cooker)
73             return True
74         except:
75             import traceback
76             return traceback.format_exc()
77
78     def runAsyncCommand(self):
79         try:
80             if self.currentAsyncCommand is not None:
81                 (command, options) = self.currentAsyncCommand
82                 commandmethod = getattr(CommandsAsync, command)
83                 needcache = getattr( commandmethod, "needcache" )
84                 if needcache and self.cooker.cookerState != bb.cooker.cookerParsed:
85                     self.cooker.updateCache()
86                     return True
87                 else:
88                     commandmethod(self.cmds_async, self, options)
89                     return False
90             else:
91                 return False
92         except KeyboardInterrupt as exc:
93             self.finishAsyncCommand("Interrupted")
94             return False
95         except SystemExit as exc:
96             arg = exc.args[0]
97             if isinstance(arg, basestring):
98                 self.finishAsyncCommand(arg)
99             else:
100                 self.finishAsyncCommand("Exited with %s" % arg)
101             return False
102         except Exception:
103             import traceback
104             self.finishAsyncCommand(traceback.format_exc())
105             return False
106
107     def finishAsyncCommand(self, error = None):
108         if error:
109             bb.event.fire(CookerCommandFailed(error), self.cooker.configuration.event_data)
110         else:
111             bb.event.fire(CookerCommandCompleted(), self.cooker.configuration.event_data)
112         self.currentAsyncCommand = None
113
114
115 class CommandsSync:
116     """
117     A class of synchronous commands
118     These should run quickly so as not to hurt interactive performance.
119     These must not influence any running synchronous command.
120     """
121
122     def stateShutdown(self, command, params):
123         """
124         Trigger cooker 'shutdown' mode
125         """
126         command.cooker.cookerAction = bb.cooker.cookerShutdown
127
128     def stateStop(self, command, params):
129         """
130         Stop the cooker
131         """
132         command.cooker.cookerAction = bb.cooker.cookerStop
133
134     def getCmdLineAction(self, command, params):
135         """
136         Get any command parsed from the commandline
137         """
138         return command.cooker.commandlineAction
139
140     def getVariable(self, command, params):
141         """
142         Read the value of a variable from configuration.data
143         """
144         varname = params[0]
145         expand = True
146         if len(params) > 1:
147             expand = params[1]
148
149         return bb.data.getVar(varname, command.cooker.configuration.data, expand)
150
151     def setVariable(self, command, params):
152         """
153         Set the value of variable in configuration.data
154         """
155         varname = params[0]
156         value = params[1]
157         bb.data.setVar(varname, value, command.cooker.configuration.data)
158
159
160 class CommandsAsync:
161     """
162     A class of asynchronous commands
163     These functions communicate via generated events.
164     Any function that requires metadata parsing should be here.
165     """
166
167     def buildFile(self, command, params):
168         """
169         Build a single specified .bb file
170         """
171         bfile = params[0]
172         task = params[1]
173
174         command.cooker.buildFile(bfile, task)
175     buildFile.needcache = False
176
177     def buildTargets(self, command, params):
178         """
179         Build a set of targets
180         """
181         pkgs_to_build = params[0]
182         task = params[1]
183
184         command.cooker.buildTargets(pkgs_to_build, task)
185     buildTargets.needcache = True
186
187     def generateDepTreeEvent(self, command, params):
188         """
189         Generate an event containing the dependency information
190         """
191         pkgs_to_build = params[0]
192         task = params[1]
193
194         command.cooker.generateDepTreeEvent(pkgs_to_build, task)
195         command.finishAsyncCommand()
196     generateDepTreeEvent.needcache = True
197
198     def generateDotGraph(self, command, params):
199         """
200         Dump dependency information to disk as .dot files
201         """
202         pkgs_to_build = params[0]
203         task = params[1]
204
205         command.cooker.generateDotGraphFiles(pkgs_to_build, task)
206         command.finishAsyncCommand()
207     generateDotGraph.needcache = True
208
209     def showVersions(self, command, params):
210         """
211         Show the currently selected versions
212         """
213         command.cooker.showVersions()
214         command.finishAsyncCommand()
215     showVersions.needcache = True
216
217     def showEnvironmentTarget(self, command, params):
218         """
219         Print the environment of a target recipe
220         (needs the cache to work out which recipe to use)
221         """
222         pkg = params[0]
223
224         command.cooker.showEnvironment(None, pkg)
225         command.finishAsyncCommand()
226     showEnvironmentTarget.needcache = True
227
228     def showEnvironment(self, command, params):
229         """
230         Print the standard environment
231         or if specified the environment for a specified recipe
232         """
233         bfile = params[0]
234
235         command.cooker.showEnvironment(bfile)
236         command.finishAsyncCommand()
237     showEnvironment.needcache = False
238
239     def parseFiles(self, command, params):
240         """
241         Parse the .bb files
242         """
243         command.cooker.updateCache()
244         command.finishAsyncCommand()
245     parseFiles.needcache = True
246
247     def compareRevisions(self, command, params):
248         """
249         Parse the .bb files
250         """
251         command.cooker.compareRevisions()
252         command.finishAsyncCommand()
253     compareRevisions.needcache = True
254
255 #
256 # Events
257 #
258 class CookerCommandCompleted(bb.event.Event):
259     """
260     Cooker command completed
261     """
262     def  __init__(self):
263         bb.event.Event.__init__(self)
264
265
266 class CookerCommandFailed(bb.event.Event):
267     """
268     Cooker command completed
269     """
270     def  __init__(self, error):
271         bb.event.Event.__init__(self)
272         self.error = error
273
274 class CookerCommandSetExitCode(bb.event.Event):
275     """
276     Set the exit code for a cooker command
277     """
278     def  __init__(self, exitcode):
279         bb.event.Event.__init__(self)
280         self.exitcode = int(exitcode)