diff options
Diffstat (limited to 'bitbake/lib/bb/command.py')
-rw-r--r-- | bitbake/lib/bb/command.py | 147 |
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 | ||
21 | from collections import OrderedDict, defaultdict | 21 | from collections import OrderedDict, defaultdict |
22 | 22 | ||
23 | import io | ||
23 | import bb.event | 24 | import bb.event |
24 | import bb.cooker | 25 | import bb.cooker |
25 | import bb.remotedata | 26 | import bb.remotedata |
27 | import bb.parse | ||
26 | 28 | ||
27 | class DataStoreConnectionHandle(object): | 29 | class 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 | |||
556 | class CommandsAsync: | 608 | class 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 | ||