blob: 0559ffc07cbf663eb1ae675cf32acc95e5a1d348 [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
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500184 def getSetVariable(self, command, params):
185 """
186 Read the value of a variable from data and set it into the datastore
187 which effectively expands and locks the value.
188 """
189 varname = params[0]
190 result = self.getVariable(command, params)
191 command.cooker.data.setVar(varname, result)
192 return result
193
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500194 def setConfig(self, command, params):
195 """
196 Set the value of variable in configuration
197 """
198 varname = params[0]
199 value = str(params[1])
200 setattr(command.cooker.configuration, varname, value)
201
202 def enableDataTracking(self, command, params):
203 """
204 Enable history tracking for variables
205 """
206 command.cooker.enableDataTracking()
207
208 def disableDataTracking(self, command, params):
209 """
210 Disable history tracking for variables
211 """
212 command.cooker.disableDataTracking()
213
214 def setPrePostConfFiles(self, command, params):
215 prefiles = params[0].split()
216 postfiles = params[1].split()
217 command.cooker.configuration.prefile = prefiles
218 command.cooker.configuration.postfile = postfiles
219 setPrePostConfFiles.needconfig = False
220
221 def getCpuCount(self, command, params):
222 """
223 Get the CPU count on the bitbake server
224 """
225 return bb.utils.cpu_count()
226 getCpuCount.readonly = True
227 getCpuCount.needconfig = False
228
229 def matchFile(self, command, params):
230 fMatch = params[0]
231 return command.cooker.matchFile(fMatch)
232 matchFile.needconfig = False
233
234 def generateNewImage(self, command, params):
235 image = params[0]
236 base_image = params[1]
237 package_queue = params[2]
238 timestamp = params[3]
239 description = params[4]
240 return command.cooker.generateNewImage(image, base_image,
241 package_queue, timestamp, description)
242
243 def ensureDir(self, command, params):
244 directory = params[0]
245 bb.utils.mkdirhier(directory)
246 ensureDir.needconfig = False
247
248 def setVarFile(self, command, params):
249 """
250 Save a variable in a file; used for saving in a configuration file
251 """
252 var = params[0]
253 val = params[1]
254 default_file = params[2]
255 op = params[3]
256 command.cooker.modifyConfigurationVar(var, val, default_file, op)
257 setVarFile.needconfig = False
258
259 def removeVarFile(self, command, params):
260 """
261 Remove a variable declaration from a file
262 """
263 var = params[0]
264 command.cooker.removeConfigurationVar(var)
265 removeVarFile.needconfig = False
266
267 def createConfigFile(self, command, params):
268 """
269 Create an extra configuration file
270 """
271 name = params[0]
272 command.cooker.createConfigFile(name)
273 createConfigFile.needconfig = False
274
275 def setEventMask(self, command, params):
276 handlerNum = params[0]
277 llevel = params[1]
278 debug_domains = params[2]
279 mask = params[3]
280 return bb.event.set_UIHmask(handlerNum, llevel, debug_domains, mask)
281 setEventMask.needconfig = False
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500282 setEventMask.readonly = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500283
284 def setFeatures(self, command, params):
285 """
286 Set the cooker features to include the passed list of features
287 """
288 features = params[0]
289 command.cooker.setFeatures(features)
290 setFeatures.needconfig = False
291 # although we change the internal state of the cooker, this is transparent since
292 # we always take and leave the cooker in state.initial
293 setFeatures.readonly = True
294
295 def updateConfig(self, command, params):
296 options = params[0]
297 environment = params[1]
298 command.cooker.updateConfigOpts(options, environment)
299 updateConfig.needconfig = False
300
301class CommandsAsync:
302 """
303 A class of asynchronous commands
304 These functions communicate via generated events.
305 Any function that requires metadata parsing should be here.
306 """
307
308 def buildFile(self, command, params):
309 """
310 Build a single specified .bb file
311 """
312 bfile = params[0]
313 task = params[1]
314
315 command.cooker.buildFile(bfile, task)
316 buildFile.needcache = False
317
318 def buildTargets(self, command, params):
319 """
320 Build a set of targets
321 """
322 pkgs_to_build = params[0]
323 task = params[1]
324
325 command.cooker.buildTargets(pkgs_to_build, task)
326 buildTargets.needcache = True
327
328 def generateDepTreeEvent(self, command, params):
329 """
330 Generate an event containing the dependency information
331 """
332 pkgs_to_build = params[0]
333 task = params[1]
334
335 command.cooker.generateDepTreeEvent(pkgs_to_build, task)
336 command.finishAsyncCommand()
337 generateDepTreeEvent.needcache = True
338
339 def generateDotGraph(self, command, params):
340 """
341 Dump dependency information to disk as .dot files
342 """
343 pkgs_to_build = params[0]
344 task = params[1]
345
346 command.cooker.generateDotGraphFiles(pkgs_to_build, task)
347 command.finishAsyncCommand()
348 generateDotGraph.needcache = True
349
350 def generateTargetsTree(self, command, params):
351 """
352 Generate a tree of buildable targets.
353 If klass is provided ensure all recipes that inherit the class are
354 included in the package list.
355 If pkg_list provided use that list (plus any extras brought in by
356 klass) rather than generating a tree for all packages.
357 """
358 klass = params[0]
359 pkg_list = params[1]
360
361 command.cooker.generateTargetsTree(klass, pkg_list)
362 command.finishAsyncCommand()
363 generateTargetsTree.needcache = True
364
365 def findCoreBaseFiles(self, command, params):
366 """
367 Find certain files in COREBASE directory. i.e. Layers
368 """
369 subdir = params[0]
370 filename = params[1]
371
372 command.cooker.findCoreBaseFiles(subdir, filename)
373 command.finishAsyncCommand()
374 findCoreBaseFiles.needcache = False
375
376 def findConfigFiles(self, command, params):
377 """
378 Find config files which provide appropriate values
379 for the passed configuration variable. i.e. MACHINE
380 """
381 varname = params[0]
382
383 command.cooker.findConfigFiles(varname)
384 command.finishAsyncCommand()
385 findConfigFiles.needcache = False
386
387 def findFilesMatchingInDir(self, command, params):
388 """
389 Find implementation files matching the specified pattern
390 in the requested subdirectory of a BBPATH
391 """
392 pattern = params[0]
393 directory = params[1]
394
395 command.cooker.findFilesMatchingInDir(pattern, directory)
396 command.finishAsyncCommand()
397 findFilesMatchingInDir.needcache = False
398
399 def findConfigFilePath(self, command, params):
400 """
401 Find the path of the requested configuration file
402 """
403 configfile = params[0]
404
405 command.cooker.findConfigFilePath(configfile)
406 command.finishAsyncCommand()
407 findConfigFilePath.needcache = False
408
409 def showVersions(self, command, params):
410 """
411 Show the currently selected versions
412 """
413 command.cooker.showVersions()
414 command.finishAsyncCommand()
415 showVersions.needcache = True
416
417 def showEnvironmentTarget(self, command, params):
418 """
419 Print the environment of a target recipe
420 (needs the cache to work out which recipe to use)
421 """
422 pkg = params[0]
423
424 command.cooker.showEnvironment(None, pkg)
425 command.finishAsyncCommand()
426 showEnvironmentTarget.needcache = True
427
428 def showEnvironment(self, command, params):
429 """
430 Print the standard environment
431 or if specified the environment for a specified recipe
432 """
433 bfile = params[0]
434
435 command.cooker.showEnvironment(bfile)
436 command.finishAsyncCommand()
437 showEnvironment.needcache = False
438
439 def parseFiles(self, command, params):
440 """
441 Parse the .bb files
442 """
443 command.cooker.updateCache()
444 command.finishAsyncCommand()
445 parseFiles.needcache = True
446
447 def compareRevisions(self, command, params):
448 """
449 Parse the .bb files
450 """
451 if bb.fetch.fetcher_compare_revisions(command.cooker.data):
452 command.finishAsyncCommand(code=1)
453 else:
454 command.finishAsyncCommand()
455 compareRevisions.needcache = True
456
457 def triggerEvent(self, command, params):
458 """
459 Trigger a certain event
460 """
461 event = params[0]
462 bb.event.fire(eval(event), command.cooker.data)
463 command.currentAsyncCommand = None
464 triggerEvent.needcache = False
465
466 def resetCooker(self, command, params):
467 """
468 Reset the cooker to its initial state, thus forcing a reparse for
469 any async command that has the needcache property set to True
470 """
471 command.cooker.reset()
472 command.finishAsyncCommand()
473 resetCooker.needcache = False
474