1""" 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# SPDX-License-Identifier: GPL-2.0-only 10# 11 12""" 13The bitbake server takes 'commands' from its UI/commandline. 14Commands are either synchronous or asynchronous. 15Async commands return data to the client in the form of events. 16Sync commands must only return data through the function return value 17and must not trigger events, directly or indirectly. 18Commands are queued in a CommandQueue 19""" 20 21from collections import OrderedDict, defaultdict 22 23import io 24import bb.event 25import bb.cooker 26import bb.remotedata 27import bb.parse 28 29class DataStoreConnectionHandle(object): 30 def __init__(self, dsindex=0): 31 self.dsindex = dsindex 32 33class CommandCompleted(bb.event.Event): 34 pass 35 36class CommandExit(bb.event.Event): 37 def __init__(self, exitcode): 38 bb.event.Event.__init__(self) 39 self.exitcode = int(exitcode) 40 41class CommandFailed(CommandExit): 42 def __init__(self, message): 43 self.error = message 44 CommandExit.__init__(self, 1) 45 def __str__(self): 46 return "Command execution failed: %s" % self.error 47 48class CommandError(Exception): 49 pass 50 51class Command: 52 """ 53 A queue of asynchronous commands for bitbake 54 """ 55 def __init__(self, cooker, process_server): 56 self.cooker = cooker 57 self.cmds_sync = CommandsSync() 58 self.cmds_async = CommandsAsync() 59 self.remotedatastores = None 60 61 self.process_server = process_server 62 # Access with locking using process_server.{get/set/clear}_async_cmd() 63 self.currentAsyncCommand = None 64 65 def runCommand(self, commandline, process_server, ro_only=False): 66 command = commandline.pop(0) 67 68 # Ensure cooker is ready for commands 69 if command not in ["updateConfig", "setFeatures", "ping"]: 70 try: 71 self.cooker.init_configdata() 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() 81 82 if hasattr(CommandsSync, command): 83 # Can run synchronous commands straight away 84 command_method = getattr(self.cmds_sync, command) 85 if ro_only: 86 if not hasattr(command_method, 'readonly') or not getattr(command_method, 'readonly'): 87 return None, "Not able to execute not readonly commands in readonly mode" 88 try: 89 if getattr(command_method, 'needconfig', True): 90 self.cooker.updateCacheSync() 91 result = command_method(self, commandline) 92 except CommandError as exc: 93 return None, exc.args[0] 94 except (Exception, SystemExit) as exc: 95 import traceback 96 if isinstance(exc, bb.BBHandledException): 97 # We need to start returning real exceptions here. Until we do, we can't 98 # tell if an exception is an instance of bb.BBHandledException 99 return None, "bb.BBHandledException()\n" + traceback.format_exc() 100 return None, traceback.format_exc() 101 else: 102 return result, None 103 if command not in CommandsAsync.__dict__: 104 return None, "No such command" 105 if not process_server.set_async_cmd((command, commandline)): 106 return None, "Busy (%s in progress)" % self.process_server.get_async_cmd()[0] 107 self.cooker.idleCallBackRegister(self.runAsyncCommand, process_server) 108 return True, None 109 110 def runAsyncCommand(self, _, process_server, halt): 111 try: 112 if self.cooker.state in (bb.cooker.State.ERROR, bb.cooker.State.SHUTDOWN, bb.cooker.State.FORCE_SHUTDOWN): 113 # updateCache will trigger a shutdown of the parser 114 # and then raise BBHandledException triggering an exit 115 self.cooker.updateCache() 116 return bb.server.process.idleFinish("Cooker in error state") 117 cmd = process_server.get_async_cmd() 118 if cmd is not None: 119 (command, options) = cmd 120 commandmethod = getattr(CommandsAsync, command) 121 needcache = getattr( commandmethod, "needcache" ) 122 if needcache and self.cooker.state != bb.cooker.State.RUNNING: 123 self.cooker.updateCache() 124 return True 125 else: 126 commandmethod(self.cmds_async, self, options) 127 return False 128 else: 129 return bb.server.process.idleFinish("Nothing to do, no async command?") 130 except KeyboardInterrupt as exc: 131 return bb.server.process.idleFinish("Interrupted") 132 except SystemExit as exc: 133 arg = exc.args[0] 134 if isinstance(arg, str): 135 return bb.server.process.idleFinish(arg) 136 else: 137 return bb.server.process.idleFinish("Exited with %s" % arg) 138 except Exception as exc: 139 import traceback 140 if isinstance(exc, bb.BBHandledException): 141 return bb.server.process.idleFinish("") 142 else: 143 return bb.server.process.idleFinish(traceback.format_exc()) 144 145 def finishAsyncCommand(self, msg=None, code=None): 146 if msg or msg == "": 147 bb.event.fire(CommandFailed(msg), self.cooker.data) 148 elif code: 149 bb.event.fire(CommandExit(code), self.cooker.data) 150 else: 151 bb.event.fire(CommandCompleted(), self.cooker.data) 152 self.cooker.finishcommand() 153 self.process_server.clear_async_cmd() 154 155 def reset(self): 156 if self.remotedatastores: 157 self.remotedatastores = bb.remotedata.RemoteDatastores(self.cooker) 158 159class CommandsSync: 160 """ 161 A class of synchronous commands 162 These should run quickly so as not to hurt interactive performance. 163 These must not influence any running synchronous command. 164 """ 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 174 def stateShutdown(self, command, params): 175 """ 176 Trigger cooker 'shutdown' mode 177 """ 178 command.cooker.shutdown(False) 179 180 def stateForceShutdown(self, command, params): 181 """ 182 Stop the cooker 183 """ 184 command.cooker.shutdown(True) 185 186 def getAllKeysWithFlags(self, command, params): 187 """ 188 Returns a dump of the global state. Call with 189 variable flags to be retrieved as params. 190 """ 191 flaglist = params[0] 192 return command.cooker.getAllKeysWithFlags(flaglist) 193 getAllKeysWithFlags.readonly = True 194 195 def getVariable(self, command, params): 196 """ 197 Read the value of a variable from data 198 """ 199 varname = params[0] 200 expand = True 201 if len(params) > 1: 202 expand = (params[1] == "True") 203 204 return command.cooker.data.getVar(varname, expand) 205 getVariable.readonly = True 206 207 def setVariable(self, command, params): 208 """ 209 Set the value of variable in data 210 """ 211 varname = params[0] 212 value = str(params[1]) 213 command.cooker.extraconfigdata[varname] = value 214 command.cooker.data.setVar(varname, value) 215 216 def getSetVariable(self, command, params): 217 """ 218 Read the value of a variable from data and set it into the datastore 219 which effectively expands and locks the value. 220 """ 221 varname = params[0] 222 result = self.getVariable(command, params) 223 command.cooker.data.setVar(varname, result) 224 return result 225 226 def setConfig(self, command, params): 227 """ 228 Set the value of variable in configuration 229 """ 230 varname = params[0] 231 value = str(params[1]) 232 setattr(command.cooker.configuration, varname, value) 233 234 def enableDataTracking(self, command, params): 235 """ 236 Enable history tracking for variables 237 """ 238 command.cooker.enableDataTracking() 239 240 def disableDataTracking(self, command, params): 241 """ 242 Disable history tracking for variables 243 """ 244 command.cooker.disableDataTracking() 245 246 def setPrePostConfFiles(self, command, params): 247 prefiles = params[0].split() 248 postfiles = params[1].split() 249 command.cooker.configuration.prefile = prefiles 250 command.cooker.configuration.postfile = postfiles 251 setPrePostConfFiles.needconfig = False 252 253 def matchFile(self, command, params): 254 fMatch = params[0] 255 try: 256 mc = params[0] 257 except IndexError: 258 mc = '' 259 return command.cooker.matchFile(fMatch, mc) 260 matchFile.needconfig = False 261 262 def getUIHandlerNum(self, command, params): 263 return bb.event.get_uihandler() 264 getUIHandlerNum.needconfig = False 265 getUIHandlerNum.readonly = True 266 267 def setEventMask(self, command, params): 268 handlerNum = params[0] 269 llevel = params[1] 270 debug_domains = params[2] 271 mask = params[3] 272 return bb.event.set_UIHmask(handlerNum, llevel, debug_domains, mask) 273 setEventMask.needconfig = False 274 setEventMask.readonly = True 275 276 def setFeatures(self, command, params): 277 """ 278 Set the cooker features to include the passed list of features 279 """ 280 features = params[0] 281 command.cooker.setFeatures(features) 282 setFeatures.needconfig = False 283 # although we change the internal state of the cooker, this is transparent since 284 # we always take and leave the cooker in state.initial 285 setFeatures.readonly = True 286 287 def updateConfig(self, command, params): 288 options = params[0] 289 environment = params[1] 290 cmdline = params[2] 291 command.cooker.updateConfigOpts(options, environment, cmdline) 292 updateConfig.needconfig = False 293 294 def parseConfiguration(self, command, params): 295 """Instruct bitbake to parse its configuration 296 NOTE: it is only necessary to call this if you aren't calling any normal action 297 (otherwise parsing is taken care of automatically) 298 """ 299 command.cooker.parseConfiguration() 300 parseConfiguration.needconfig = False 301 302 def getLayerPriorities(self, command, params): 303 command.cooker.parseConfiguration() 304 ret = [] 305 # regex objects cannot be marshalled by xmlrpc 306 for collection, pattern, regex, pri in command.cooker.bbfile_config_priorities: 307 ret.append((collection, pattern, regex.pattern, pri)) 308 return ret 309 getLayerPriorities.readonly = True 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 316 def getRecipes(self, command, params): 317 try: 318 mc = params[0] 319 except IndexError: 320 mc = '' 321 return list(command.cooker.recipecaches[mc].pkg_pn.items()) 322 getRecipes.readonly = True 323 324 def getRecipeDepends(self, command, params): 325 try: 326 mc = params[0] 327 except IndexError: 328 mc = '' 329 return list(command.cooker.recipecaches[mc].deps.items()) 330 getRecipeDepends.readonly = True 331 332 def getRecipeVersions(self, command, params): 333 try: 334 mc = params[0] 335 except IndexError: 336 mc = '' 337 return command.cooker.recipecaches[mc].pkg_pepvpr 338 getRecipeVersions.readonly = True 339 340 def getRecipeProvides(self, command, params): 341 try: 342 mc = params[0] 343 except IndexError: 344 mc = '' 345 return command.cooker.recipecaches[mc].fn_provides 346 getRecipeProvides.readonly = True 347 348 def getRecipePackages(self, command, params): 349 try: 350 mc = params[0] 351 except IndexError: 352 mc = '' 353 return command.cooker.recipecaches[mc].packages 354 getRecipePackages.readonly = True 355 356 def getRecipePackagesDynamic(self, command, params): 357 try: 358 mc = params[0] 359 except IndexError: 360 mc = '' 361 return command.cooker.recipecaches[mc].packages_dynamic 362 getRecipePackagesDynamic.readonly = True 363 364 def getRProviders(self, command, params): 365 try: 366 mc = params[0] 367 except IndexError: 368 mc = '' 369 return command.cooker.recipecaches[mc].rproviders 370 getRProviders.readonly = True 371 372 def getRuntimeDepends(self, command, params): 373 ret = [] 374 try: 375 mc = params[0] 376 except IndexError: 377 mc = '' 378 rundeps = command.cooker.recipecaches[mc].rundeps 379 for key, value in rundeps.items(): 380 if isinstance(value, defaultdict): 381 value = dict(value) 382 ret.append((key, value)) 383 return ret 384 getRuntimeDepends.readonly = True 385 386 def getRuntimeRecommends(self, command, params): 387 ret = [] 388 try: 389 mc = params[0] 390 except IndexError: 391 mc = '' 392 runrecs = command.cooker.recipecaches[mc].runrecs 393 for key, value in runrecs.items(): 394 if isinstance(value, defaultdict): 395 value = dict(value) 396 ret.append((key, value)) 397 return ret 398 getRuntimeRecommends.readonly = True 399 400 def getRecipeInherits(self, command, params): 401 try: 402 mc = params[0] 403 except IndexError: 404 mc = '' 405 return command.cooker.recipecaches[mc].inherits 406 getRecipeInherits.readonly = True 407 408 def getBbFilePriority(self, command, params): 409 try: 410 mc = params[0] 411 except IndexError: 412 mc = '' 413 return command.cooker.recipecaches[mc].bbfile_priority 414 getBbFilePriority.readonly = True 415 416 def getDefaultPreference(self, command, params): 417 try: 418 mc = params[0] 419 except IndexError: 420 mc = '' 421 return command.cooker.recipecaches[mc].pkg_dp 422 getDefaultPreference.readonly = True 423 424 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 440 # Return list sorted by reverse priority order 441 import bb.cache 442 def sortkey(x): 443 vfn, _ = x 444 realfn, _, item_mc = bb.cache.virtualfn2realfn(vfn) 445 return -command.cooker.collections[item_mc].calc_bbfile_priority(realfn)[0], vfn 446 447 skipdict = OrderedDict(sorted(command.cooker.skiplist_by_mc[mc].items(), key=sortkey)) 448 return list(skipdict.items()) 449 getSkippedRecipes.readonly = True 450 451 def getOverlayedRecipes(self, command, params): 452 try: 453 mc = params[0] 454 except IndexError: 455 mc = '' 456 return list(command.cooker.collections[mc].overlayed.items()) 457 getOverlayedRecipes.readonly = True 458 459 def getFileAppends(self, command, params): 460 fn = params[0] 461 try: 462 mc = params[1] 463 except IndexError: 464 mc = '' 465 return command.cooker.collections[mc].get_file_appends(fn) 466 getFileAppends.readonly = True 467 468 def getAllAppends(self, command, params): 469 try: 470 mc = params[0] 471 except IndexError: 472 mc = '' 473 return command.cooker.collections[mc].bbappends 474 getAllAppends.readonly = True 475 476 def findProviders(self, command, params): 477 try: 478 mc = params[0] 479 except IndexError: 480 mc = '' 481 return command.cooker.findProviders(mc) 482 findProviders.readonly = True 483 484 def findBestProvider(self, command, params): 485 (mc, pn) = bb.runqueue.split_mc(params[0]) 486 return command.cooker.findBestProvider(pn, mc) 487 findBestProvider.readonly = True 488 489 def allProviders(self, command, params): 490 try: 491 mc = params[0] 492 except IndexError: 493 mc = '' 494 return list(bb.providers.allProviders(command.cooker.recipecaches[mc]).items()) 495 allProviders.readonly = True 496 497 def getRuntimeProviders(self, command, params): 498 rprovide = params[0] 499 try: 500 mc = params[1] 501 except IndexError: 502 mc = '' 503 all_p = bb.providers.getRuntimeProviders(command.cooker.recipecaches[mc], rprovide) 504 if all_p: 505 best = bb.providers.filterProvidersRunTime(all_p, rprovide, 506 command.cooker.data, 507 command.cooker.recipecaches[mc])[0][0] 508 else: 509 best = None 510 return all_p, best 511 getRuntimeProviders.readonly = True 512 513 def dataStoreConnectorCmd(self, command, params): 514 dsindex = params[0] 515 method = params[1] 516 args = params[2] 517 kwargs = params[3] 518 519 d = command.remotedatastores[dsindex] 520 ret = getattr(d, method)(*args, **kwargs) 521 522 if isinstance(ret, bb.data_smart.DataSmart): 523 idx = command.remotedatastores.store(ret) 524 return DataStoreConnectionHandle(idx) 525 526 return ret 527 528 def dataStoreConnectorVarHistCmd(self, command, params): 529 dsindex = params[0] 530 method = params[1] 531 args = params[2] 532 kwargs = params[3] 533 534 d = command.remotedatastores[dsindex].varhistory 535 return getattr(d, method)(*args, **kwargs) 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 548 def dataStoreConnectorIncHistCmd(self, command, params): 549 dsindex = params[0] 550 method = params[1] 551 args = params[2] 552 kwargs = params[3] 553 554 d = command.remotedatastores[dsindex].inchistory 555 return getattr(d, method)(*args, **kwargs) 556 557 def dataStoreConnectorRelease(self, command, params): 558 dsindex = params[0] 559 if dsindex <= 0: 560 raise CommandError('dataStoreConnectorRelease: invalid index %d' % dsindex) 561 command.remotedatastores.release(dsindex) 562 563 def parseRecipeFile(self, command, params): 564 """ 565 Parse the specified recipe file (with or without bbappends) 566 and return a datastore object representing the environment 567 for the recipe. 568 """ 569 virtualfn = params[0] 570 (fn, cls, mc) = bb.cache.virtualfn2realfn(virtualfn) 571 appends = params[1] 572 appendlist = params[2] 573 if len(params) > 3: 574 config_data = command.remotedatastores[params[3]] 575 else: 576 config_data = None 577 578 if appends: 579 if appendlist is not None: 580 appendfiles = appendlist 581 else: 582 appendfiles = command.cooker.collections[mc].get_file_appends(fn) 583 else: 584 appendfiles = [] 585 layername = command.cooker.collections[mc].calc_bbfile_priority(fn)[2] 586 # We are calling bb.cache locally here rather than on the server, 587 # but that's OK because it doesn't actually need anything from 588 # the server barring the global datastore (which we have a remote 589 # version of) 590 if config_data: 591 # We have to use a different function here if we're passing in a datastore 592 # NOTE: we took a copy above, so we don't do it here again 593 envdata = command.cooker.databuilder._parse_recipe(config_data, fn, appendfiles, mc, layername)[cls] 594 else: 595 # Use the standard path 596 envdata = command.cooker.databuilder.parseRecipe(virtualfn, appendfiles, layername) 597 idx = command.remotedatastores.store(envdata) 598 return DataStoreConnectionHandle(idx) 599 parseRecipeFile.readonly = True 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 608class CommandsAsync: 609 """ 610 A class of asynchronous commands 611 These functions communicate via generated events. 612 Any function that requires metadata parsing should be here. 613 """ 614 615 def buildFile(self, command, params): 616 """ 617 Build a single specified .bb file 618 """ 619 bfile = params[0] 620 task = params[1] 621 if len(params) > 2: 622 internal = params[2] 623 else: 624 internal = False 625 626 if internal: 627 command.cooker.buildFileInternal(bfile, task, fireevents=False, quietlog=True) 628 else: 629 command.cooker.buildFile(bfile, task) 630 buildFile.needcache = False 631 632 def buildTargets(self, command, params): 633 """ 634 Build a set of targets 635 """ 636 pkgs_to_build = params[0] 637 task = params[1] 638 639 command.cooker.buildTargets(pkgs_to_build, task) 640 buildTargets.needcache = True 641 642 def generateDepTreeEvent(self, command, params): 643 """ 644 Generate an event containing the dependency information 645 """ 646 pkgs_to_build = params[0] 647 task = params[1] 648 649 command.cooker.generateDepTreeEvent(pkgs_to_build, task) 650 command.finishAsyncCommand() 651 generateDepTreeEvent.needcache = True 652 653 def generateDotGraph(self, command, params): 654 """ 655 Dump dependency information to disk as .dot files 656 """ 657 pkgs_to_build = params[0] 658 task = params[1] 659 660 command.cooker.generateDotGraphFiles(pkgs_to_build, task) 661 command.finishAsyncCommand() 662 generateDotGraph.needcache = True 663 664 def generateTargetsTree(self, command, params): 665 """ 666 Generate a tree of buildable targets. 667 If klass is provided ensure all recipes that inherit the class are 668 included in the package list. 669 If pkg_list provided use that list (plus any extras brought in by 670 klass) rather than generating a tree for all packages. 671 """ 672 klass = params[0] 673 pkg_list = params[1] 674 675 command.cooker.generateTargetsTree(klass, pkg_list) 676 command.finishAsyncCommand() 677 generateTargetsTree.needcache = True 678 679 def findConfigFiles(self, command, params): 680 """ 681 Find config files which provide appropriate values 682 for the passed configuration variable. i.e. MACHINE 683 """ 684 varname = params[0] 685 686 command.cooker.findConfigFiles(varname) 687 command.finishAsyncCommand() 688 findConfigFiles.needcache = False 689 690 def findFilesMatchingInDir(self, command, params): 691 """ 692 Find implementation files matching the specified pattern 693 in the requested subdirectory of a BBPATH 694 """ 695 pattern = params[0] 696 directory = params[1] 697 698 command.cooker.findFilesMatchingInDir(pattern, directory) 699 command.finishAsyncCommand() 700 findFilesMatchingInDir.needcache = False 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 712 def findConfigFilePath(self, command, params): 713 """ 714 Find the path of the requested configuration file 715 """ 716 configfile = params[0] 717 718 command.cooker.findConfigFilePath(configfile) 719 command.finishAsyncCommand() 720 findConfigFilePath.needcache = False 721 722 def showVersions(self, command, params): 723 """ 724 Show the currently selected versions 725 """ 726 command.cooker.showVersions() 727 command.finishAsyncCommand() 728 showVersions.needcache = True 729 730 def showEnvironmentTarget(self, command, params): 731 """ 732 Print the environment of a target recipe 733 (needs the cache to work out which recipe to use) 734 """ 735 pkg = params[0] 736 737 command.cooker.showEnvironment(None, pkg) 738 command.finishAsyncCommand() 739 showEnvironmentTarget.needcache = True 740 741 def showEnvironment(self, command, params): 742 """ 743 Print the standard environment 744 or if specified the environment for a specified recipe 745 """ 746 bfile = params[0] 747 748 command.cooker.showEnvironment(bfile) 749 command.finishAsyncCommand() 750 showEnvironment.needcache = False 751 752 def parseFiles(self, command, params): 753 """ 754 Parse the .bb files 755 """ 756 command.cooker.updateCache() 757 command.finishAsyncCommand() 758 parseFiles.needcache = True 759 760 def compareRevisions(self, command, params): 761 """ 762 Parse the .bb files 763 """ 764 if bb.fetch.fetcher_compare_revisions(command.cooker.data): 765 command.finishAsyncCommand(code=1) 766 else: 767 command.finishAsyncCommand() 768 compareRevisions.needcache = True 769 770 def triggerEvent(self, command, params): 771 """ 772 Trigger a certain event 773 """ 774 event = params[0] 775 bb.event.fire(eval(event), command.cooker.data) 776 process_server.clear_async_cmd() 777 triggerEvent.needcache = False 778 779 def resetCooker(self, command, params): 780 """ 781 Reset the cooker to its initial state, thus forcing a reparse for 782 any async command that has the needcache property set to True 783 """ 784 command.cooker.reset() 785 command.finishAsyncCommand() 786 resetCooker.needcache = False 787 788 def clientComplete(self, command, params): 789 """ 790 Do the right thing when the controlling client exits 791 """ 792 command.cooker.clientComplete() 793 command.finishAsyncCommand() 794 clientComplete.needcache = False 795 796 def findSigInfo(self, command, params): 797 """ 798 Find signature info files via the signature generator 799 """ 800 (mc, pn) = bb.runqueue.split_mc(params[0]) 801 taskname = params[1] 802 sigs = params[2] 803 bb.siggen.check_siggen_version(bb.siggen) 804 res = bb.siggen.find_siginfo(pn, taskname, sigs, command.cooker.databuilder.mcdata[mc]) 805 bb.event.fire(bb.event.FindSigInfoResult(res), command.cooker.databuilder.mcdata[mc]) 806 command.finishAsyncCommand() 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 814