123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755 |
- """
- BitBake 'Command' module
- Provide an interface to interact with the bitbake server through 'commands'
- """
- # Copyright (C) 2006-2007 Richard Purdie
- #
- # SPDX-License-Identifier: GPL-2.0-only
- #
- """
- The bitbake server takes 'commands' from its UI/commandline.
- Commands are either synchronous or asynchronous.
- Async commands return data to the client in the form of events.
- Sync commands must only return data through the function return value
- and must not trigger events, directly or indirectly.
- Commands are queued in a CommandQueue
- """
- from collections import OrderedDict, defaultdict
- import bb.event
- import bb.cooker
- import bb.remotedata
- class DataStoreConnectionHandle(object):
- def __init__(self, dsindex=0):
- self.dsindex = dsindex
- class CommandCompleted(bb.event.Event):
- pass
- class CommandExit(bb.event.Event):
- def __init__(self, exitcode):
- bb.event.Event.__init__(self)
- self.exitcode = int(exitcode)
- class CommandFailed(CommandExit):
- def __init__(self, message):
- self.error = message
- CommandExit.__init__(self, 1)
- def __str__(self):
- return "Command execution failed: %s" % self.error
- class CommandError(Exception):
- pass
- class Command:
- """
- A queue of asynchronous commands for bitbake
- """
- def __init__(self, cooker):
- self.cooker = cooker
- self.cmds_sync = CommandsSync()
- self.cmds_async = CommandsAsync()
- self.remotedatastores = bb.remotedata.RemoteDatastores(cooker)
- # FIXME Add lock for this
- self.currentAsyncCommand = None
- def runCommand(self, commandline, ro_only = False):
- command = commandline.pop(0)
- if hasattr(CommandsSync, command):
- # Can run synchronous commands straight away
- command_method = getattr(self.cmds_sync, command)
- if ro_only:
- if not hasattr(command_method, 'readonly') or False == getattr(command_method, 'readonly'):
- return None, "Not able to execute not readonly commands in readonly mode"
- try:
- self.cooker.process_inotify_updates()
- if getattr(command_method, 'needconfig', True):
- self.cooker.updateCacheSync()
- result = command_method(self, commandline)
- except CommandError as exc:
- return None, exc.args[0]
- except (Exception, SystemExit):
- import traceback
- return None, traceback.format_exc()
- else:
- return result, None
- if self.currentAsyncCommand is not None:
- return None, "Busy (%s in progress)" % self.currentAsyncCommand[0]
- if command not in CommandsAsync.__dict__:
- return None, "No such command"
- self.currentAsyncCommand = (command, commandline)
- self.cooker.configuration.server_register_idlecallback(self.cooker.runCommands, self.cooker)
- return True, None
- def runAsyncCommand(self):
- try:
- self.cooker.process_inotify_updates()
- if self.cooker.state in (bb.cooker.state.error, bb.cooker.state.shutdown, bb.cooker.state.forceshutdown):
- # updateCache will trigger a shutdown of the parser
- # and then raise BBHandledException triggering an exit
- self.cooker.updateCache()
- return False
- if self.currentAsyncCommand is not None:
- (command, options) = self.currentAsyncCommand
- commandmethod = getattr(CommandsAsync, command)
- needcache = getattr( commandmethod, "needcache" )
- if needcache and self.cooker.state != bb.cooker.state.running:
- self.cooker.updateCache()
- return True
- else:
- commandmethod(self.cmds_async, self, options)
- return False
- else:
- return False
- except KeyboardInterrupt as exc:
- self.finishAsyncCommand("Interrupted")
- return False
- except SystemExit as exc:
- arg = exc.args[0]
- if isinstance(arg, str):
- self.finishAsyncCommand(arg)
- else:
- self.finishAsyncCommand("Exited with %s" % arg)
- return False
- except Exception as exc:
- import traceback
- if isinstance(exc, bb.BBHandledException):
- self.finishAsyncCommand("")
- else:
- self.finishAsyncCommand(traceback.format_exc())
- return False
- def finishAsyncCommand(self, msg=None, code=None):
- if msg or msg == "":
- bb.event.fire(CommandFailed(msg), self.cooker.data)
- elif code:
- bb.event.fire(CommandExit(code), self.cooker.data)
- else:
- bb.event.fire(CommandCompleted(), self.cooker.data)
- self.currentAsyncCommand = None
- self.cooker.finishcommand()
- def reset(self):
- self.remotedatastores = bb.remotedata.RemoteDatastores(self.cooker)
- def split_mc_pn(pn):
- if pn.startswith("multiconfig:"):
- _, mc, pn = pn.split(":", 2)
- return (mc, pn)
- return ('', pn)
- class CommandsSync:
- """
- A class of synchronous commands
- These should run quickly so as not to hurt interactive performance.
- These must not influence any running synchronous command.
- """
- def stateShutdown(self, command, params):
- """
- Trigger cooker 'shutdown' mode
- """
- command.cooker.shutdown(False)
- def stateForceShutdown(self, command, params):
- """
- Stop the cooker
- """
- command.cooker.shutdown(True)
- def getAllKeysWithFlags(self, command, params):
- """
- Returns a dump of the global state. Call with
- variable flags to be retrieved as params.
- """
- flaglist = params[0]
- return command.cooker.getAllKeysWithFlags(flaglist)
- getAllKeysWithFlags.readonly = True
- def getVariable(self, command, params):
- """
- Read the value of a variable from data
- """
- varname = params[0]
- expand = True
- if len(params) > 1:
- expand = (params[1] == "True")
- return command.cooker.data.getVar(varname, expand)
- getVariable.readonly = True
- def setVariable(self, command, params):
- """
- Set the value of variable in data
- """
- varname = params[0]
- value = str(params[1])
- command.cooker.extraconfigdata[varname] = value
- command.cooker.data.setVar(varname, value)
- def getSetVariable(self, command, params):
- """
- Read the value of a variable from data and set it into the datastore
- which effectively expands and locks the value.
- """
- varname = params[0]
- result = self.getVariable(command, params)
- command.cooker.data.setVar(varname, result)
- return result
- def setConfig(self, command, params):
- """
- Set the value of variable in configuration
- """
- varname = params[0]
- value = str(params[1])
- setattr(command.cooker.configuration, varname, value)
- def enableDataTracking(self, command, params):
- """
- Enable history tracking for variables
- """
- command.cooker.enableDataTracking()
- def disableDataTracking(self, command, params):
- """
- Disable history tracking for variables
- """
- command.cooker.disableDataTracking()
- def setPrePostConfFiles(self, command, params):
- prefiles = params[0].split()
- postfiles = params[1].split()
- command.cooker.configuration.prefile = prefiles
- command.cooker.configuration.postfile = postfiles
- setPrePostConfFiles.needconfig = False
- def matchFile(self, command, params):
- fMatch = params[0]
- return command.cooker.matchFile(fMatch)
- matchFile.needconfig = False
- def getUIHandlerNum(self, command, params):
- return bb.event.get_uihandler()
- getUIHandlerNum.needconfig = False
- getUIHandlerNum.readonly = True
- def setEventMask(self, command, params):
- handlerNum = params[0]
- llevel = params[1]
- debug_domains = params[2]
- mask = params[3]
- return bb.event.set_UIHmask(handlerNum, llevel, debug_domains, mask)
- setEventMask.needconfig = False
- setEventMask.readonly = True
- def setFeatures(self, command, params):
- """
- Set the cooker features to include the passed list of features
- """
- features = params[0]
- command.cooker.setFeatures(features)
- setFeatures.needconfig = False
- # although we change the internal state of the cooker, this is transparent since
- # we always take and leave the cooker in state.initial
- setFeatures.readonly = True
- def updateConfig(self, command, params):
- options = params[0]
- environment = params[1]
- cmdline = params[2]
- command.cooker.updateConfigOpts(options, environment, cmdline)
- updateConfig.needconfig = False
- def parseConfiguration(self, command, params):
- """Instruct bitbake to parse its configuration
- NOTE: it is only necessary to call this if you aren't calling any normal action
- (otherwise parsing is taken care of automatically)
- """
- command.cooker.parseConfiguration()
- parseConfiguration.needconfig = False
- def getLayerPriorities(self, command, params):
- command.cooker.parseConfiguration()
- ret = []
- # regex objects cannot be marshalled by xmlrpc
- for collection, pattern, regex, pri in command.cooker.bbfile_config_priorities:
- ret.append((collection, pattern, regex.pattern, pri))
- return ret
- getLayerPriorities.readonly = True
- def getRecipes(self, command, params):
- try:
- mc = params[0]
- except IndexError:
- mc = ''
- return list(command.cooker.recipecaches[mc].pkg_pn.items())
- getRecipes.readonly = True
- def getRecipeDepends(self, command, params):
- try:
- mc = params[0]
- except IndexError:
- mc = ''
- return list(command.cooker.recipecaches[mc].deps.items())
- getRecipeDepends.readonly = True
- def getRecipeVersions(self, command, params):
- try:
- mc = params[0]
- except IndexError:
- mc = ''
- return command.cooker.recipecaches[mc].pkg_pepvpr
- getRecipeVersions.readonly = True
- def getRecipeProvides(self, command, params):
- try:
- mc = params[0]
- except IndexError:
- mc = ''
- return command.cooker.recipecaches[mc].fn_provides
- getRecipeProvides.readonly = True
- def getRecipePackages(self, command, params):
- try:
- mc = params[0]
- except IndexError:
- mc = ''
- return command.cooker.recipecaches[mc].packages
- getRecipePackages.readonly = True
- def getRecipePackagesDynamic(self, command, params):
- try:
- mc = params[0]
- except IndexError:
- mc = ''
- return command.cooker.recipecaches[mc].packages_dynamic
- getRecipePackagesDynamic.readonly = True
- def getRProviders(self, command, params):
- try:
- mc = params[0]
- except IndexError:
- mc = ''
- return command.cooker.recipecaches[mc].rproviders
- getRProviders.readonly = True
- def getRuntimeDepends(self, command, params):
- ret = []
- try:
- mc = params[0]
- except IndexError:
- mc = ''
- rundeps = command.cooker.recipecaches[mc].rundeps
- for key, value in rundeps.items():
- if isinstance(value, defaultdict):
- value = dict(value)
- ret.append((key, value))
- return ret
- getRuntimeDepends.readonly = True
- def getRuntimeRecommends(self, command, params):
- ret = []
- try:
- mc = params[0]
- except IndexError:
- mc = ''
- runrecs = command.cooker.recipecaches[mc].runrecs
- for key, value in runrecs.items():
- if isinstance(value, defaultdict):
- value = dict(value)
- ret.append((key, value))
- return ret
- getRuntimeRecommends.readonly = True
- def getRecipeInherits(self, command, params):
- try:
- mc = params[0]
- except IndexError:
- mc = ''
- return command.cooker.recipecaches[mc].inherits
- getRecipeInherits.readonly = True
- def getBbFilePriority(self, command, params):
- try:
- mc = params[0]
- except IndexError:
- mc = ''
- return command.cooker.recipecaches[mc].bbfile_priority
- getBbFilePriority.readonly = True
- def getDefaultPreference(self, command, params):
- try:
- mc = params[0]
- except IndexError:
- mc = ''
- return command.cooker.recipecaches[mc].pkg_dp
- getDefaultPreference.readonly = True
- def getSkippedRecipes(self, command, params):
- # Return list sorted by reverse priority order
- import bb.cache
- skipdict = OrderedDict(sorted(command.cooker.skiplist.items(),
- key=lambda x: (-command.cooker.collection.calc_bbfile_priority(bb.cache.virtualfn2realfn(x[0])[0]), x[0])))
- return list(skipdict.items())
- getSkippedRecipes.readonly = True
- def getOverlayedRecipes(self, command, params):
- return list(command.cooker.collection.overlayed.items())
- getOverlayedRecipes.readonly = True
- def getFileAppends(self, command, params):
- fn = params[0]
- return command.cooker.collection.get_file_appends(fn)
- getFileAppends.readonly = True
- def getAllAppends(self, command, params):
- return command.cooker.collection.bbappends
- getAllAppends.readonly = True
- def findProviders(self, command, params):
- return command.cooker.findProviders()
- findProviders.readonly = True
- def findBestProvider(self, command, params):
- (mc, pn) = split_mc_pn(params[0])
- return command.cooker.findBestProvider(pn, mc)
- findBestProvider.readonly = True
- def allProviders(self, command, params):
- try:
- mc = params[0]
- except IndexError:
- mc = ''
- return list(bb.providers.allProviders(command.cooker.recipecaches[mc]).items())
- allProviders.readonly = True
- def getRuntimeProviders(self, command, params):
- rprovide = params[0]
- try:
- mc = params[1]
- except IndexError:
- mc = ''
- all_p = bb.providers.getRuntimeProviders(command.cooker.recipecaches[mc], rprovide)
- if all_p:
- best = bb.providers.filterProvidersRunTime(all_p, rprovide,
- command.cooker.data,
- command.cooker.recipecaches[mc])[0][0]
- else:
- best = None
- return all_p, best
- getRuntimeProviders.readonly = True
- def dataStoreConnectorFindVar(self, command, params):
- dsindex = params[0]
- name = params[1]
- datastore = command.remotedatastores[dsindex]
- value, overridedata = datastore._findVar(name)
- if value:
- content = value.get('_content', None)
- if isinstance(content, bb.data_smart.DataSmart):
- # Value is a datastore (e.g. BB_ORIGENV) - need to handle this carefully
- idx = command.remotedatastores.check_store(content, True)
- return {'_content': DataStoreConnectionHandle(idx),
- '_connector_origtype': 'DataStoreConnectionHandle',
- '_connector_overrides': overridedata}
- elif isinstance(content, set):
- return {'_content': list(content),
- '_connector_origtype': 'set',
- '_connector_overrides': overridedata}
- else:
- value['_connector_overrides'] = overridedata
- else:
- value = {}
- value['_connector_overrides'] = overridedata
- return value
- dataStoreConnectorFindVar.readonly = True
- def dataStoreConnectorGetKeys(self, command, params):
- dsindex = params[0]
- datastore = command.remotedatastores[dsindex]
- return list(datastore.keys())
- dataStoreConnectorGetKeys.readonly = True
- def dataStoreConnectorGetVarHistory(self, command, params):
- dsindex = params[0]
- name = params[1]
- datastore = command.remotedatastores[dsindex]
- return datastore.varhistory.variable(name)
- dataStoreConnectorGetVarHistory.readonly = True
- def dataStoreConnectorExpandPythonRef(self, command, params):
- config_data_dict = params[0]
- varname = params[1]
- expr = params[2]
- config_data = command.remotedatastores.receive_datastore(config_data_dict)
- varparse = bb.data_smart.VariableParse(varname, config_data)
- return varparse.python_sub(expr)
- def dataStoreConnectorRelease(self, command, params):
- dsindex = params[0]
- if dsindex <= 0:
- raise CommandError('dataStoreConnectorRelease: invalid index %d' % dsindex)
- command.remotedatastores.release(dsindex)
- def dataStoreConnectorSetVarFlag(self, command, params):
- dsindex = params[0]
- name = params[1]
- flag = params[2]
- value = params[3]
- datastore = command.remotedatastores[dsindex]
- datastore.setVarFlag(name, flag, value)
- def dataStoreConnectorDelVar(self, command, params):
- dsindex = params[0]
- name = params[1]
- datastore = command.remotedatastores[dsindex]
- if len(params) > 2:
- flag = params[2]
- datastore.delVarFlag(name, flag)
- else:
- datastore.delVar(name)
- def dataStoreConnectorRenameVar(self, command, params):
- dsindex = params[0]
- name = params[1]
- newname = params[2]
- datastore = command.remotedatastores[dsindex]
- datastore.renameVar(name, newname)
- def parseRecipeFile(self, command, params):
- """
- Parse the specified recipe file (with or without bbappends)
- and return a datastore object representing the environment
- for the recipe.
- """
- fn = params[0]
- appends = params[1]
- appendlist = params[2]
- if len(params) > 3:
- config_data_dict = params[3]
- config_data = command.remotedatastores.receive_datastore(config_data_dict)
- else:
- config_data = None
- if appends:
- if appendlist is not None:
- appendfiles = appendlist
- else:
- appendfiles = command.cooker.collection.get_file_appends(fn)
- else:
- appendfiles = []
- # We are calling bb.cache locally here rather than on the server,
- # but that's OK because it doesn't actually need anything from
- # the server barring the global datastore (which we have a remote
- # version of)
- if config_data:
- # We have to use a different function here if we're passing in a datastore
- # NOTE: we took a copy above, so we don't do it here again
- envdata = bb.cache.parse_recipe(config_data, fn, appendfiles)['']
- else:
- # Use the standard path
- parser = bb.cache.NoCache(command.cooker.databuilder)
- envdata = parser.loadDataFull(fn, appendfiles)
- idx = command.remotedatastores.store(envdata)
- return DataStoreConnectionHandle(idx)
- parseRecipeFile.readonly = True
- class CommandsAsync:
- """
- A class of asynchronous commands
- These functions communicate via generated events.
- Any function that requires metadata parsing should be here.
- """
- def buildFile(self, command, params):
- """
- Build a single specified .bb file
- """
- bfile = params[0]
- task = params[1]
- if len(params) > 2:
- internal = params[2]
- else:
- internal = False
- if internal:
- command.cooker.buildFileInternal(bfile, task, fireevents=False, quietlog=True)
- else:
- command.cooker.buildFile(bfile, task)
- buildFile.needcache = False
- def buildTargets(self, command, params):
- """
- Build a set of targets
- """
- pkgs_to_build = params[0]
- task = params[1]
- command.cooker.buildTargets(pkgs_to_build, task)
- buildTargets.needcache = True
- def generateDepTreeEvent(self, command, params):
- """
- Generate an event containing the dependency information
- """
- pkgs_to_build = params[0]
- task = params[1]
- command.cooker.generateDepTreeEvent(pkgs_to_build, task)
- command.finishAsyncCommand()
- generateDepTreeEvent.needcache = True
- def generateDotGraph(self, command, params):
- """
- Dump dependency information to disk as .dot files
- """
- pkgs_to_build = params[0]
- task = params[1]
- command.cooker.generateDotGraphFiles(pkgs_to_build, task)
- command.finishAsyncCommand()
- generateDotGraph.needcache = True
- def generateTargetsTree(self, command, params):
- """
- Generate a tree of buildable targets.
- If klass is provided ensure all recipes that inherit the class are
- included in the package list.
- If pkg_list provided use that list (plus any extras brought in by
- klass) rather than generating a tree for all packages.
- """
- klass = params[0]
- pkg_list = params[1]
- command.cooker.generateTargetsTree(klass, pkg_list)
- command.finishAsyncCommand()
- generateTargetsTree.needcache = True
- def findConfigFiles(self, command, params):
- """
- Find config files which provide appropriate values
- for the passed configuration variable. i.e. MACHINE
- """
- varname = params[0]
- command.cooker.findConfigFiles(varname)
- command.finishAsyncCommand()
- findConfigFiles.needcache = False
- def findFilesMatchingInDir(self, command, params):
- """
- Find implementation files matching the specified pattern
- in the requested subdirectory of a BBPATH
- """
- pattern = params[0]
- directory = params[1]
- command.cooker.findFilesMatchingInDir(pattern, directory)
- command.finishAsyncCommand()
- findFilesMatchingInDir.needcache = False
- def findConfigFilePath(self, command, params):
- """
- Find the path of the requested configuration file
- """
- configfile = params[0]
- command.cooker.findConfigFilePath(configfile)
- command.finishAsyncCommand()
- findConfigFilePath.needcache = False
- def showVersions(self, command, params):
- """
- Show the currently selected versions
- """
- command.cooker.showVersions()
- command.finishAsyncCommand()
- showVersions.needcache = True
- def showEnvironmentTarget(self, command, params):
- """
- Print the environment of a target recipe
- (needs the cache to work out which recipe to use)
- """
- pkg = params[0]
- command.cooker.showEnvironment(None, pkg)
- command.finishAsyncCommand()
- showEnvironmentTarget.needcache = True
- def showEnvironment(self, command, params):
- """
- Print the standard environment
- or if specified the environment for a specified recipe
- """
- bfile = params[0]
- command.cooker.showEnvironment(bfile)
- command.finishAsyncCommand()
- showEnvironment.needcache = False
- def parseFiles(self, command, params):
- """
- Parse the .bb files
- """
- command.cooker.updateCache()
- command.finishAsyncCommand()
- parseFiles.needcache = True
- def compareRevisions(self, command, params):
- """
- Parse the .bb files
- """
- if bb.fetch.fetcher_compare_revisions(command.cooker.data):
- command.finishAsyncCommand(code=1)
- else:
- command.finishAsyncCommand()
- compareRevisions.needcache = True
- def triggerEvent(self, command, params):
- """
- Trigger a certain event
- """
- event = params[0]
- bb.event.fire(eval(event), command.cooker.data)
- command.currentAsyncCommand = None
- triggerEvent.needcache = False
- def resetCooker(self, command, params):
- """
- Reset the cooker to its initial state, thus forcing a reparse for
- any async command that has the needcache property set to True
- """
- command.cooker.reset()
- command.finishAsyncCommand()
- resetCooker.needcache = False
- def clientComplete(self, command, params):
- """
- Do the right thing when the controlling client exits
- """
- command.cooker.clientComplete()
- command.finishAsyncCommand()
- clientComplete.needcache = False
- def findSigInfo(self, command, params):
- """
- Find signature info files via the signature generator
- """
- pn = params[0]
- taskname = params[1]
- sigs = params[2]
- res = bb.siggen.find_siginfo(pn, taskname, sigs, command.cooker.data)
- bb.event.fire(bb.event.FindSigInfoResult(res), command.cooker.data)
- command.finishAsyncCommand()
- findSigInfo.needcache = False
|