summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/command.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/bb/command.py')
-rw-r--r--bitbake/lib/bb/command.py147
1 files changed, 108 insertions, 39 deletions
diff --git a/bitbake/lib/bb/command.py b/bitbake/lib/bb/command.py
index dd77cdd6e2..59a979ee90 100644
--- a/bitbake/lib/bb/command.py
+++ b/bitbake/lib/bb/command.py
@@ -20,9 +20,11 @@ Commands are queued in a CommandQueue
20 20
21from collections import OrderedDict, defaultdict 21from collections import OrderedDict, defaultdict
22 22
23import io
23import bb.event 24import bb.event
24import bb.cooker 25import bb.cooker
25import bb.remotedata 26import bb.remotedata
27import bb.parse
26 28
27class DataStoreConnectionHandle(object): 29class DataStoreConnectionHandle(object):
28 def __init__(self, dsindex=0): 30 def __init__(self, dsindex=0):
@@ -50,23 +52,32 @@ class Command:
50 """ 52 """
51 A queue of asynchronous commands for bitbake 53 A queue of asynchronous commands for bitbake
52 """ 54 """
53 def __init__(self, cooker): 55 def __init__(self, cooker, process_server):
54 self.cooker = cooker 56 self.cooker = cooker
55 self.cmds_sync = CommandsSync() 57 self.cmds_sync = CommandsSync()
56 self.cmds_async = CommandsAsync() 58 self.cmds_async = CommandsAsync()
57 self.remotedatastores = None 59 self.remotedatastores = None
58 60
59 # FIXME Add lock for this 61 self.process_server = process_server
62 # Access with locking using process_server.{get/set/clear}_async_cmd()
60 self.currentAsyncCommand = None 63 self.currentAsyncCommand = None
61 64
62 def runCommand(self, commandline, ro_only = False): 65 def runCommand(self, commandline, process_server, ro_only=False):
63 command = commandline.pop(0) 66 command = commandline.pop(0)
64 67
65 # Ensure cooker is ready for commands 68 # Ensure cooker is ready for commands
66 if command != "updateConfig" and command != "setFeatures": 69 if command not in ["updateConfig", "setFeatures", "ping"]:
67 self.cooker.init_configdata() 70 try:
68 if not self.remotedatastores: 71 self.cooker.init_configdata()
69 self.remotedatastores = bb.remotedata.RemoteDatastores(self.cooker) 72 if not self.remotedatastores:
73 self.remotedatastores = bb.remotedata.RemoteDatastores(self.cooker)
74 except (Exception, SystemExit) as exc:
75 import traceback
76 if isinstance(exc, bb.BBHandledException):
77 # We need to start returning real exceptions here. Until we do, we can't
78 # tell if an exception is an instance of bb.BBHandledException
79 return None, "bb.BBHandledException()\n" + traceback.format_exc()
80 return None, traceback.format_exc()
70 81
71 if hasattr(CommandsSync, command): 82 if hasattr(CommandsSync, command):
72 # Can run synchronous commands straight away 83 # Can run synchronous commands straight away
@@ -75,7 +86,6 @@ class Command:
75 if not hasattr(command_method, 'readonly') or not getattr(command_method, 'readonly'): 86 if not hasattr(command_method, 'readonly') or not getattr(command_method, 'readonly'):
76 return None, "Not able to execute not readonly commands in readonly mode" 87 return None, "Not able to execute not readonly commands in readonly mode"
77 try: 88 try:
78 self.cooker.process_inotify_updates()
79 if getattr(command_method, 'needconfig', True): 89 if getattr(command_method, 'needconfig', True):
80 self.cooker.updateCacheSync() 90 self.cooker.updateCacheSync()
81 result = command_method(self, commandline) 91 result = command_method(self, commandline)
@@ -90,61 +100,57 @@ class Command:
90 return None, traceback.format_exc() 100 return None, traceback.format_exc()
91 else: 101 else:
92 return result, None 102 return result, None
93 if self.currentAsyncCommand is not None:
94 return None, "Busy (%s in progress)" % self.currentAsyncCommand[0]
95 if command not in CommandsAsync.__dict__: 103 if command not in CommandsAsync.__dict__:
96 return None, "No such command" 104 return None, "No such command"
97 self.currentAsyncCommand = (command, commandline) 105 if not process_server.set_async_cmd((command, commandline)):
98 self.cooker.idleCallBackRegister(self.cooker.runCommands, self.cooker) 106 return None, "Busy (%s in progress)" % self.process_server.get_async_cmd()[0]
107 self.cooker.idleCallBackRegister(self.runAsyncCommand, process_server)
99 return True, None 108 return True, None
100 109
101 def runAsyncCommand(self): 110 def runAsyncCommand(self, _, process_server, halt):
102 try: 111 try:
103 self.cooker.process_inotify_updates() 112 if self.cooker.state in (bb.cooker.State.ERROR, bb.cooker.State.SHUTDOWN, bb.cooker.State.FORCE_SHUTDOWN):
104 if self.cooker.state in (bb.cooker.state.error, bb.cooker.state.shutdown, bb.cooker.state.forceshutdown):
105 # updateCache will trigger a shutdown of the parser 113 # updateCache will trigger a shutdown of the parser
106 # and then raise BBHandledException triggering an exit 114 # and then raise BBHandledException triggering an exit
107 self.cooker.updateCache() 115 self.cooker.updateCache()
108 return False 116 return bb.server.process.idleFinish("Cooker in error state")
109 if self.currentAsyncCommand is not None: 117 cmd = process_server.get_async_cmd()
110 (command, options) = self.currentAsyncCommand 118 if cmd is not None:
119 (command, options) = cmd
111 commandmethod = getattr(CommandsAsync, command) 120 commandmethod = getattr(CommandsAsync, command)
112 needcache = getattr( commandmethod, "needcache" ) 121 needcache = getattr( commandmethod, "needcache" )
113 if needcache and self.cooker.state != bb.cooker.state.running: 122 if needcache and self.cooker.state != bb.cooker.State.RUNNING:
114 self.cooker.updateCache() 123 self.cooker.updateCache()
115 return True 124 return True
116 else: 125 else:
117 commandmethod(self.cmds_async, self, options) 126 commandmethod(self.cmds_async, self, options)
118 return False 127 return False
119 else: 128 else:
120 return False 129 return bb.server.process.idleFinish("Nothing to do, no async command?")
121 except KeyboardInterrupt as exc: 130 except KeyboardInterrupt as exc:
122 self.finishAsyncCommand("Interrupted") 131 return bb.server.process.idleFinish("Interrupted")
123 return False
124 except SystemExit as exc: 132 except SystemExit as exc:
125 arg = exc.args[0] 133 arg = exc.args[0]
126 if isinstance(arg, str): 134 if isinstance(arg, str):
127 self.finishAsyncCommand(arg) 135 return bb.server.process.idleFinish(arg)
128 else: 136 else:
129 self.finishAsyncCommand("Exited with %s" % arg) 137 return bb.server.process.idleFinish("Exited with %s" % arg)
130 return False
131 except Exception as exc: 138 except Exception as exc:
132 import traceback 139 import traceback
133 if isinstance(exc, bb.BBHandledException): 140 if isinstance(exc, bb.BBHandledException):
134 self.finishAsyncCommand("") 141 return bb.server.process.idleFinish("")
135 else: 142 else:
136 self.finishAsyncCommand(traceback.format_exc()) 143 return bb.server.process.idleFinish(traceback.format_exc())
137 return False
138 144
139 def finishAsyncCommand(self, msg=None, code=None): 145 def finishAsyncCommand(self, msg=None, code=None):
146 self.cooker.finishcommand()
147 self.process_server.clear_async_cmd()
140 if msg or msg == "": 148 if msg or msg == "":
141 bb.event.fire(CommandFailed(msg), self.cooker.data) 149 bb.event.fire(CommandFailed(msg), self.cooker.data)
142 elif code: 150 elif code:
143 bb.event.fire(CommandExit(code), self.cooker.data) 151 bb.event.fire(CommandExit(code), self.cooker.data)
144 else: 152 else:
145 bb.event.fire(CommandCompleted(), self.cooker.data) 153 bb.event.fire(CommandCompleted(), self.cooker.data)
146 self.currentAsyncCommand = None
147 self.cooker.finishcommand()
148 154
149 def reset(self): 155 def reset(self):
150 if self.remotedatastores: 156 if self.remotedatastores:
@@ -157,6 +163,14 @@ class CommandsSync:
157 These must not influence any running synchronous command. 163 These must not influence any running synchronous command.
158 """ 164 """
159 165
166 def ping(self, command, params):
167 """
168 Allow a UI to check the server is still alive
169 """
170 return "Still alive!"
171 ping.needconfig = False
172 ping.readonly = True
173
160 def stateShutdown(self, command, params): 174 def stateShutdown(self, command, params):
161 """ 175 """
162 Trigger cooker 'shutdown' mode 176 Trigger cooker 'shutdown' mode
@@ -294,6 +308,11 @@ class CommandsSync:
294 return ret 308 return ret
295 getLayerPriorities.readonly = True 309 getLayerPriorities.readonly = True
296 310
311 def revalidateCaches(self, command, params):
312 """Called by UI clients when metadata may have changed"""
313 command.cooker.revalidateCaches()
314 revalidateCaches.needconfig = False
315
297 def getRecipes(self, command, params): 316 def getRecipes(self, command, params):
298 try: 317 try:
299 mc = params[0] 318 mc = params[0]
@@ -402,15 +421,30 @@ class CommandsSync:
402 return command.cooker.recipecaches[mc].pkg_dp 421 return command.cooker.recipecaches[mc].pkg_dp
403 getDefaultPreference.readonly = True 422 getDefaultPreference.readonly = True
404 423
424
405 def getSkippedRecipes(self, command, params): 425 def getSkippedRecipes(self, command, params):
426 """
427 Get the map of skipped recipes for the specified multiconfig/mc name (`params[0]`).
428
429 Invoked by `bb.tinfoil.Tinfoil.get_skipped_recipes`
430
431 :param command: Internally used parameter.
432 :param params: Parameter array. params[0] is multiconfig/mc name. If not given, then default mc '' is assumed.
433 :return: Dict whose keys are virtualfns and values are `bb.cooker.SkippedPackage`
434 """
435 try:
436 mc = params[0]
437 except IndexError:
438 mc = ''
439
406 # Return list sorted by reverse priority order 440 # Return list sorted by reverse priority order
407 import bb.cache 441 import bb.cache
408 def sortkey(x): 442 def sortkey(x):
409 vfn, _ = x 443 vfn, _ = x
410 realfn, _, mc = bb.cache.virtualfn2realfn(vfn) 444 realfn, _, item_mc = bb.cache.virtualfn2realfn(vfn)
411 return (-command.cooker.collections[mc].calc_bbfile_priority(realfn)[0], vfn) 445 return -command.cooker.collections[item_mc].calc_bbfile_priority(realfn)[0], vfn
412 446
413 skipdict = OrderedDict(sorted(command.cooker.skiplist.items(), key=sortkey)) 447 skipdict = OrderedDict(sorted(command.cooker.skiplist_by_mc[mc].items(), key=sortkey))
414 return list(skipdict.items()) 448 return list(skipdict.items())
415 getSkippedRecipes.readonly = True 449 getSkippedRecipes.readonly = True
416 450
@@ -500,6 +534,17 @@ class CommandsSync:
500 d = command.remotedatastores[dsindex].varhistory 534 d = command.remotedatastores[dsindex].varhistory
501 return getattr(d, method)(*args, **kwargs) 535 return getattr(d, method)(*args, **kwargs)
502 536
537 def dataStoreConnectorVarHistCmdEmit(self, command, params):
538 dsindex = params[0]
539 var = params[1]
540 oval = params[2]
541 val = params[3]
542 d = command.remotedatastores[params[4]]
543
544 o = io.StringIO()
545 command.remotedatastores[dsindex].varhistory.emit(var, oval, val, o, d)
546 return o.getvalue()
547
503 def dataStoreConnectorIncHistCmd(self, command, params): 548 def dataStoreConnectorIncHistCmd(self, command, params):
504 dsindex = params[0] 549 dsindex = params[0]
505 method = params[1] 550 method = params[1]
@@ -521,8 +566,8 @@ class CommandsSync:
521 and return a datastore object representing the environment 566 and return a datastore object representing the environment
522 for the recipe. 567 for the recipe.
523 """ 568 """
524 fn = params[0] 569 virtualfn = params[0]
525 mc = bb.runqueue.mc_from_tid(fn) 570 (fn, cls, mc) = bb.cache.virtualfn2realfn(virtualfn)
526 appends = params[1] 571 appends = params[1]
527 appendlist = params[2] 572 appendlist = params[2]
528 if len(params) > 3: 573 if len(params) > 3:
@@ -537,6 +582,7 @@ class CommandsSync:
537 appendfiles = command.cooker.collections[mc].get_file_appends(fn) 582 appendfiles = command.cooker.collections[mc].get_file_appends(fn)
538 else: 583 else:
539 appendfiles = [] 584 appendfiles = []
585 layername = command.cooker.collections[mc].calc_bbfile_priority(fn)[2]
540 # We are calling bb.cache locally here rather than on the server, 586 # We are calling bb.cache locally here rather than on the server,
541 # but that's OK because it doesn't actually need anything from 587 # but that's OK because it doesn't actually need anything from
542 # the server barring the global datastore (which we have a remote 588 # the server barring the global datastore (which we have a remote
@@ -544,15 +590,21 @@ class CommandsSync:
544 if config_data: 590 if config_data:
545 # We have to use a different function here if we're passing in a datastore 591 # We have to use a different function here if we're passing in a datastore
546 # NOTE: we took a copy above, so we don't do it here again 592 # NOTE: we took a copy above, so we don't do it here again
547 envdata = bb.cache.parse_recipe(config_data, fn, appendfiles, mc)[''] 593 envdata = command.cooker.databuilder._parse_recipe(config_data, fn, appendfiles, mc, layername)[cls]
548 else: 594 else:
549 # Use the standard path 595 # Use the standard path
550 parser = bb.cache.NoCache(command.cooker.databuilder) 596 envdata = command.cooker.databuilder.parseRecipe(virtualfn, appendfiles, layername)
551 envdata = parser.loadDataFull(fn, appendfiles)
552 idx = command.remotedatastores.store(envdata) 597 idx = command.remotedatastores.store(envdata)
553 return DataStoreConnectionHandle(idx) 598 return DataStoreConnectionHandle(idx)
554 parseRecipeFile.readonly = True 599 parseRecipeFile.readonly = True
555 600
601 def finalizeData(self, command, params):
602 newdata = command.cooker.data.createCopy()
603 bb.data.expandKeys(newdata)
604 bb.parse.ast.runAnonFuncs(newdata)
605 idx = command.remotedatastores.store(newdata)
606 return DataStoreConnectionHandle(idx)
607
556class CommandsAsync: 608class CommandsAsync:
557 """ 609 """
558 A class of asynchronous commands 610 A class of asynchronous commands
@@ -647,6 +699,16 @@ class CommandsAsync:
647 command.finishAsyncCommand() 699 command.finishAsyncCommand()
648 findFilesMatchingInDir.needcache = False 700 findFilesMatchingInDir.needcache = False
649 701
702 def testCookerCommandEvent(self, command, params):
703 """
704 Dummy command used by OEQA selftest to test tinfoil without IO
705 """
706 pattern = params[0]
707
708 command.cooker.testCookerCommandEvent(pattern)
709 command.finishAsyncCommand()
710 testCookerCommandEvent.needcache = False
711
650 def findConfigFilePath(self, command, params): 712 def findConfigFilePath(self, command, params):
651 """ 713 """
652 Find the path of the requested configuration file 714 Find the path of the requested configuration file
@@ -711,7 +773,7 @@ class CommandsAsync:
711 """ 773 """
712 event = params[0] 774 event = params[0]
713 bb.event.fire(eval(event), command.cooker.data) 775 bb.event.fire(eval(event), command.cooker.data)
714 command.currentAsyncCommand = None 776 process_server.clear_async_cmd()
715 triggerEvent.needcache = False 777 triggerEvent.needcache = False
716 778
717 def resetCooker(self, command, params): 779 def resetCooker(self, command, params):
@@ -738,7 +800,14 @@ class CommandsAsync:
738 (mc, pn) = bb.runqueue.split_mc(params[0]) 800 (mc, pn) = bb.runqueue.split_mc(params[0])
739 taskname = params[1] 801 taskname = params[1]
740 sigs = params[2] 802 sigs = params[2]
803 bb.siggen.check_siggen_version(bb.siggen)
741 res = bb.siggen.find_siginfo(pn, taskname, sigs, command.cooker.databuilder.mcdata[mc]) 804 res = bb.siggen.find_siginfo(pn, taskname, sigs, command.cooker.databuilder.mcdata[mc])
742 bb.event.fire(bb.event.FindSigInfoResult(res), command.cooker.databuilder.mcdata[mc]) 805 bb.event.fire(bb.event.FindSigInfoResult(res), command.cooker.databuilder.mcdata[mc])
743 command.finishAsyncCommand() 806 command.finishAsyncCommand()
744 findSigInfo.needcache = False 807 findSigInfo.needcache = False
808
809 def getTaskSignatures(self, command, params):
810 res = command.cooker.getTaskSignatures(params[0], params[1])
811 bb.event.fire(bb.event.GetTaskSignatureResult(res), command.cooker.data)
812 command.finishAsyncCommand()
813 getTaskSignatures.needcache = True