blob: 398c1d6a6ec51a6511ad39e25404de96cbc853e5 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001"""
2BitBake 'Command' module
3
4Provide 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"""
23The bitbake server takes 'commands' from its UI/commandline.
24Commands are either synchronous or asynchronous.
25Async commands return data to the client in the form of events.
26Sync commands must only return data through the function return value
27and must not trigger events, directly or indirectly.
28Commands are queued in a CommandQueue
29"""
30
31import bb.event
32import bb.cooker
33
34class CommandCompleted(bb.event.Event):
35 pass
36
37class CommandExit(bb.event.Event):
38 def __init__(self, exitcode):
39 bb.event.Event.__init__(self)
40 self.exitcode = int(exitcode)
41
42class CommandFailed(CommandExit):
43 def __init__(self, message):
44 self.error = message
45 CommandExit.__init__(self, 1)
46
47class CommandError(Exception):
48 pass
49
50class Command:
51 """
52 A queue of asynchronous commands for bitbake
53 """
54 def __init__(self, cooker):
55 self.cooker = cooker
56 self.cmds_sync = CommandsSync()
57 self.cmds_async = CommandsAsync()
58
59 # FIXME Add lock for this
60 self.currentAsyncCommand = None
61
62 def runCommand(self, commandline, ro_only = False):
63 command = commandline.pop(0)
64 if hasattr(CommandsSync, command):
65 # Can run synchronous commands straight away
66 command_method = getattr(self.cmds_sync, command)
67 if ro_only:
68 if not hasattr(command_method, 'readonly') or False == getattr(command_method, 'readonly'):
69 return None, "Not able to execute not readonly commands in readonly mode"
70 try:
71 if getattr(command_method, 'needconfig', False):
72 self.cooker.updateCacheSync()
73 result = command_method(self, commandline)
74 except CommandError as exc:
75 return None, exc.args[0]
76 except (Exception, SystemExit):
77 import traceback
78 return None, traceback.format_exc()
79 else:
80 return result, None
81 if self.currentAsyncCommand is not None:
82 return None, "Busy (%s in progress)" % self.currentAsyncCommand[0]
83 if command not in CommandsAsync.__dict__:
84 return None, "No such command"
85 self.currentAsyncCommand = (command, commandline)
86 self.cooker.configuration.server_register_idlecallback(self.cooker.runCommands, self.cooker)
87 return True, None
88
89 def runAsyncCommand(self):
90 try:
91 if self.cooker.state in (bb.cooker.state.error, bb.cooker.state.shutdown, bb.cooker.state.forceshutdown):
92 # updateCache will trigger a shutdown of the parser
93 # and then raise BBHandledException triggering an exit
94 self.cooker.updateCache()
95 return False
96 if self.currentAsyncCommand is not None:
97 (command, options) = self.currentAsyncCommand
98 commandmethod = getattr(CommandsAsync, command)
99 needcache = getattr( commandmethod, "needcache" )
100 if needcache and self.cooker.state != bb.cooker.state.running:
101 self.cooker.updateCache()
102 return True
103 else:
104 commandmethod(self.cmds_async, self, options)
105 return False
106 else:
107 return False
108 except KeyboardInterrupt as exc:
109 self.finishAsyncCommand("Interrupted")
110 return False
111 except SystemExit as exc:
112 arg = exc.args[0]
113 if isinstance(arg, basestring):
114 self.finishAsyncCommand(arg)
115 else:
116 self.finishAsyncCommand("Exited with %s" % arg)
117 return False
118 except Exception as exc:
119 import traceback
120 if isinstance(exc, bb.BBHandledException):
121 self.finishAsyncCommand("")
122 else:
123 self.finishAsyncCommand(traceback.format_exc())
124 return False
125
126 def finishAsyncCommand(self, msg=None, code=None):
127 if msg or msg == "":
128 bb.event.fire(CommandFailed(msg), self.cooker.expanded_data)
129 elif code:
130 bb.event.fire(CommandExit(code), self.cooker.expanded_data)
131 else:
132 bb.event.fire(CommandCompleted(), self.cooker.expanded_data)
133 self.currentAsyncCommand = None
134 self.cooker.finishcommand()
135
136class CommandsSync:
137 """
138 A class of synchronous commands
139 These should run quickly so as not to hurt interactive performance.
140 These must not influence any running synchronous command.
141 """
142
143 def stateShutdown(self, command, params):
144 """
145 Trigger cooker 'shutdown' mode
146 """
147 command.cooker.shutdown(False)
148
149 def stateForceShutdown(self, command, params):
150 """
151 Stop the cooker
152 """
153 command.cooker.shutdown(True)
154
155 def getAllKeysWithFlags(self, command, params):
156 """
157 Returns a dump of the global state. Call with
158 variable flags to be retrieved as params.
159 """
160 flaglist = params[0]
161 return command.cooker.getAllKeysWithFlags(flaglist)
162 getAllKeysWithFlags.readonly = True
163
164 def getVariable(self, command, params):
165 """
166 Read the value of a variable from data
167 """
168 varname = params[0]
169 expand = True
170 if len(params) > 1:
171 expand = (params[1] == "True")
172
173 return command.cooker.data.getVar(varname, expand)
174 getVariable.readonly = True
175
176 def setVariable(self, command, params):
177 """
178 Set the value of variable in data
179 """
180 varname = params[0]
181 value = str(params[1])
182 command.cooker.data.setVar(varname, value)
183
184 def setConfig(self, command, params):
185 """
186 Set the value of variable in configuration
187 """
188 varname = params[0]
189 value = str(params[1])
190 setattr(command.cooker.configuration, varname, value)
191
192 def enableDataTracking(self, command, params):
193 """
194 Enable history tracking for variables
195 """
196 command.cooker.enableDataTracking()
197
198 def disableDataTracking(self, command, params):
199 """
200 Disable history tracking for variables
201 """
202 command.cooker.disableDataTracking()
203
204 def setPrePostConfFiles(self, command, params):
205 prefiles = params[0].split()
206 postfiles = params[1].split()
207 command.cooker.configuration.prefile = prefiles
208 command.cooker.configuration.postfile = postfiles
209 setPrePostConfFiles.needconfig = False
210
211 def getCpuCount(self, command, params):
212 """
213 Get the CPU count on the bitbake server
214 """
215 return bb.utils.cpu_count()
216 getCpuCount.readonly = True
217 getCpuCount.needconfig = False
218
219 def matchFile(self, command, params):
220 fMatch = params[0]
221 return command.cooker.matchFile(fMatch)
222 matchFile.needconfig = False
223
224 def generateNewImage(self, command, params):
225 image = params[0]
226 base_image = params[1]
227 package_queue = params[2]
228 timestamp = params[3]
229 description = params[4]
230 return command.cooker.generateNewImage(image, base_image,
231 package_queue, timestamp, description)
232
233 def ensureDir(self, command, params):
234 directory = params[0]
235 bb.utils.mkdirhier(directory)
236 ensureDir.needconfig = False
237
238 def setVarFile(self, command, params):
239 """
240 Save a variable in a file; used for saving in a configuration file
241 """
242 var = params[0]
243 val = params[1]
244 default_file = params[2]
245 op = params[3]
246 command.cooker.modifyConfigurationVar(var, val, default_file, op)
247 setVarFile.needconfig = False
248
249 def removeVarFile(self, command, params):
250 """
251 Remove a variable declaration from a file
252 """
253 var = params[0]
254 command.cooker.removeConfigurationVar(var)
255 removeVarFile.needconfig = False
256
257 def createConfigFile(self, command, params):
258 """
259 Create an extra configuration file
260 """
261 name = params[0]
262 command.cooker.createConfigFile(name)
263 createConfigFile.needconfig = False
264
265 def setEventMask(self, command, params):
266 handlerNum = params[0]
267 llevel = params[1]
268 debug_domains = params[2]
269 mask = params[3]
270 return bb.event.set_UIHmask(handlerNum, llevel, debug_domains, mask)
271 setEventMask.needconfig = False
272
273 def setFeatures(self, command, params):
274 """
275 Set the cooker features to include the passed list of features
276 """
277 features = params[0]
278 command.cooker.setFeatures(features)
279 setFeatures.needconfig = False
280 # although we change the internal state of the cooker, this is transparent since
281 # we always take and leave the cooker in state.initial
282 setFeatures.readonly = True
283
284 def updateConfig(self, command, params):
285 options = params[0]
286 environment = params[1]
287 command.cooker.updateConfigOpts(options, environment)
288 updateConfig.needconfig = False
289
290class CommandsAsync:
291 """
292 A class of asynchronous commands
293 These functions communicate via generated events.
294 Any function that requires metadata parsing should be here.
295 """
296
297 def buildFile(self, command, params):
298 """
299 Build a single specified .bb file
300 """
301 bfile = params[0]
302 task = params[1]
303
304 command.cooker.buildFile(bfile, task)
305 buildFile.needcache = False
306
307 def buildTargets(self, command, params):
308 """
309 Build a set of targets
310 """
311 pkgs_to_build = params[0]
312 task = params[1]
313
314 command.cooker.buildTargets(pkgs_to_build, task)
315 buildTargets.needcache = True
316
317 def generateDepTreeEvent(self, command, params):
318 """
319 Generate an event containing the dependency information
320 """
321 pkgs_to_build = params[0]
322 task = params[1]
323
324 command.cooker.generateDepTreeEvent(pkgs_to_build, task)
325 command.finishAsyncCommand()
326 generateDepTreeEvent.needcache = True
327
328 def generateDotGraph(self, command, params):
329 """
330 Dump dependency information to disk as .dot files
331 """
332 pkgs_to_build = params[0]
333 task = params[1]
334
335 command.cooker.generateDotGraphFiles(pkgs_to_build, task)
336 command.finishAsyncCommand()
337 generateDotGraph.needcache = True
338
339 def generateTargetsTree(self, command, params):
340 """
341 Generate a tree of buildable targets.
342 If klass is provided ensure all recipes that inherit the class are
343 included in the package list.
344 If pkg_list provided use that list (plus any extras brought in by
345 klass) rather than generating a tree for all packages.
346 """
347 klass = params[0]
348 pkg_list = params[1]
349
350 command.cooker.generateTargetsTree(klass, pkg_list)
351 command.finishAsyncCommand()
352 generateTargetsTree.needcache = True
353
354 def findCoreBaseFiles(self, command, params):
355 """
356 Find certain files in COREBASE directory. i.e. Layers
357 """
358 subdir = params[0]
359 filename = params[1]
360
361 command.cooker.findCoreBaseFiles(subdir, filename)
362 command.finishAsyncCommand()
363 findCoreBaseFiles.needcache = False
364
365 def findConfigFiles(self, command, params):
366 """
367 Find config files which provide appropriate values
368 for the passed configuration variable. i.e. MACHINE
369 """
370 varname = params[0]
371
372 command.cooker.findConfigFiles(varname)
373 command.finishAsyncCommand()
374 findConfigFiles.needcache = False
375
376 def findFilesMatchingInDir(self, command, params):
377 """
378 Find implementation files matching the specified pattern
379 in the requested subdirectory of a BBPATH
380 """
381 pattern = params[0]
382 directory = params[1]
383
384 command.cooker.findFilesMatchingInDir(pattern, directory)
385 command.finishAsyncCommand()
386 findFilesMatchingInDir.needcache = False
387
388 def findConfigFilePath(self, command, params):
389 """
390 Find the path of the requested configuration file
391 """
392 configfile = params[0]
393
394 command.cooker.findConfigFilePath(configfile)
395 command.finishAsyncCommand()
396 findConfigFilePath.needcache = False
397
398 def showVersions(self, command, params):
399 """
400 Show the currently selected versions
401 """
402 command.cooker.showVersions()
403 command.finishAsyncCommand()
404 showVersions.needcache = True
405
406 def showEnvironmentTarget(self, command, params):
407 """
408 Print the environment of a target recipe
409 (needs the cache to work out which recipe to use)
410 """
411 pkg = params[0]
412
413 command.cooker.showEnvironment(None, pkg)
414 command.finishAsyncCommand()
415 showEnvironmentTarget.needcache = True
416
417 def showEnvironment(self, command, params):
418 """
419 Print the standard environment
420 or if specified the environment for a specified recipe
421 """
422 bfile = params[0]
423
424 command.cooker.showEnvironment(bfile)
425 command.finishAsyncCommand()
426 showEnvironment.needcache = False
427
428 def parseFiles(self, command, params):
429 """
430 Parse the .bb files
431 """
432 command.cooker.updateCache()
433 command.finishAsyncCommand()
434 parseFiles.needcache = True
435
436 def compareRevisions(self, command, params):
437 """
438 Parse the .bb files
439 """
440 if bb.fetch.fetcher_compare_revisions(command.cooker.data):
441 command.finishAsyncCommand(code=1)
442 else:
443 command.finishAsyncCommand()
444 compareRevisions.needcache = True
445
446 def triggerEvent(self, command, params):
447 """
448 Trigger a certain event
449 """
450 event = params[0]
451 bb.event.fire(eval(event), command.cooker.data)
452 command.currentAsyncCommand = None
453 triggerEvent.needcache = False
454
455 def resetCooker(self, command, params):
456 """
457 Reset the cooker to its initial state, thus forcing a reparse for
458 any async command that has the needcache property set to True
459 """
460 command.cooker.reset()
461 command.finishAsyncCommand()
462 resetCooker.needcache = False
463