blob: 74106d14344251ffe7b75627f710c08a3840f8f7 [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
282
283 def setFeatures(self, command, params):
284 """
285 Set the cooker features to include the passed list of features
286 """
287 features = params[0]
288 command.cooker.setFeatures(features)
289 setFeatures.needconfig = False
290 # although we change the internal state of the cooker, this is transparent since
291 # we always take and leave the cooker in state.initial
292 setFeatures.readonly = True
293
294 def updateConfig(self, command, params):
295 options = params[0]
296 environment = params[1]
297 command.cooker.updateConfigOpts(options, environment)
298 updateConfig.needconfig = False
299
300class CommandsAsync:
301 """
302 A class of asynchronous commands
303 These functions communicate via generated events.
304 Any function that requires metadata parsing should be here.
305 """
306
307 def buildFile(self, command, params):
308 """
309 Build a single specified .bb file
310 """
311 bfile = params[0]
312 task = params[1]
313
314 command.cooker.buildFile(bfile, task)
315 buildFile.needcache = False
316
317 def buildTargets(self, command, params):
318 """
319 Build a set of targets
320 """
321 pkgs_to_build = params[0]
322 task = params[1]
323
324 command.cooker.buildTargets(pkgs_to_build, task)
325 buildTargets.needcache = True
326
327 def generateDepTreeEvent(self, command, params):
328 """
329 Generate an event containing the dependency information
330 """
331 pkgs_to_build = params[0]
332 task = params[1]
333
334 command.cooker.generateDepTreeEvent(pkgs_to_build, task)
335 command.finishAsyncCommand()
336 generateDepTreeEvent.needcache = True
337
338 def generateDotGraph(self, command, params):
339 """
340 Dump dependency information to disk as .dot files
341 """
342 pkgs_to_build = params[0]
343 task = params[1]
344
345 command.cooker.generateDotGraphFiles(pkgs_to_build, task)
346 command.finishAsyncCommand()
347 generateDotGraph.needcache = True
348
349 def generateTargetsTree(self, command, params):
350 """
351 Generate a tree of buildable targets.
352 If klass is provided ensure all recipes that inherit the class are
353 included in the package list.
354 If pkg_list provided use that list (plus any extras brought in by
355 klass) rather than generating a tree for all packages.
356 """
357 klass = params[0]
358 pkg_list = params[1]
359
360 command.cooker.generateTargetsTree(klass, pkg_list)
361 command.finishAsyncCommand()
362 generateTargetsTree.needcache = True
363
364 def findCoreBaseFiles(self, command, params):
365 """
366 Find certain files in COREBASE directory. i.e. Layers
367 """
368 subdir = params[0]
369 filename = params[1]
370
371 command.cooker.findCoreBaseFiles(subdir, filename)
372 command.finishAsyncCommand()
373 findCoreBaseFiles.needcache = False
374
375 def findConfigFiles(self, command, params):
376 """
377 Find config files which provide appropriate values
378 for the passed configuration variable. i.e. MACHINE
379 """
380 varname = params[0]
381
382 command.cooker.findConfigFiles(varname)
383 command.finishAsyncCommand()
384 findConfigFiles.needcache = False
385
386 def findFilesMatchingInDir(self, command, params):
387 """
388 Find implementation files matching the specified pattern
389 in the requested subdirectory of a BBPATH
390 """
391 pattern = params[0]
392 directory = params[1]
393
394 command.cooker.findFilesMatchingInDir(pattern, directory)
395 command.finishAsyncCommand()
396 findFilesMatchingInDir.needcache = False
397
398 def findConfigFilePath(self, command, params):
399 """
400 Find the path of the requested configuration file
401 """
402 configfile = params[0]
403
404 command.cooker.findConfigFilePath(configfile)
405 command.finishAsyncCommand()
406 findConfigFilePath.needcache = False
407
408 def showVersions(self, command, params):
409 """
410 Show the currently selected versions
411 """
412 command.cooker.showVersions()
413 command.finishAsyncCommand()
414 showVersions.needcache = True
415
416 def showEnvironmentTarget(self, command, params):
417 """
418 Print the environment of a target recipe
419 (needs the cache to work out which recipe to use)
420 """
421 pkg = params[0]
422
423 command.cooker.showEnvironment(None, pkg)
424 command.finishAsyncCommand()
425 showEnvironmentTarget.needcache = True
426
427 def showEnvironment(self, command, params):
428 """
429 Print the standard environment
430 or if specified the environment for a specified recipe
431 """
432 bfile = params[0]
433
434 command.cooker.showEnvironment(bfile)
435 command.finishAsyncCommand()
436 showEnvironment.needcache = False
437
438 def parseFiles(self, command, params):
439 """
440 Parse the .bb files
441 """
442 command.cooker.updateCache()
443 command.finishAsyncCommand()
444 parseFiles.needcache = True
445
446 def compareRevisions(self, command, params):
447 """
448 Parse the .bb files
449 """
450 if bb.fetch.fetcher_compare_revisions(command.cooker.data):
451 command.finishAsyncCommand(code=1)
452 else:
453 command.finishAsyncCommand()
454 compareRevisions.needcache = True
455
456 def triggerEvent(self, command, params):
457 """
458 Trigger a certain event
459 """
460 event = params[0]
461 bb.event.fire(eval(event), command.cooker.data)
462 command.currentAsyncCommand = None
463 triggerEvent.needcache = False
464
465 def resetCooker(self, command, params):
466 """
467 Reset the cooker to its initial state, thus forcing a reparse for
468 any async command that has the needcache property set to True
469 """
470 command.cooker.reset()
471 command.finishAsyncCommand()
472 resetCooker.needcache = False
473