Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1 | # tinfoil: a simple wrapper around cooker for bitbake-based command-line utilities |
| 2 | # |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 3 | # Copyright (C) 2012-2017 Intel Corporation |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 4 | # Copyright (C) 2011 Mentor Graphics Corporation |
| 5 | # |
| 6 | # This program is free software; you can redistribute it and/or modify |
| 7 | # it under the terms of the GNU General Public License version 2 as |
| 8 | # published by the Free Software Foundation. |
| 9 | # |
| 10 | # This program is distributed in the hope that it will be useful, |
| 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | # GNU General Public License for more details. |
| 14 | # |
| 15 | # You should have received a copy of the GNU General Public License along |
| 16 | # with this program; if not, write to the Free Software Foundation, Inc., |
| 17 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| 18 | |
| 19 | import logging |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 20 | import os |
| 21 | import sys |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 22 | import atexit |
| 23 | import re |
| 24 | from collections import OrderedDict, defaultdict |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 25 | |
| 26 | import bb.cache |
| 27 | import bb.cooker |
| 28 | import bb.providers |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 29 | import bb.taskdata |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 30 | import bb.utils |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 31 | import bb.command |
| 32 | import bb.remotedata |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 33 | from bb.cookerdata import CookerConfiguration, ConfigParameters |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 34 | from bb.main import setup_bitbake, BitBakeConfigParameters, BBMainException |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 35 | import bb.fetch2 |
| 36 | |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 37 | |
| 38 | # We need this in order to shut down the connection to the bitbake server, |
| 39 | # otherwise the process will never properly exit |
| 40 | _server_connections = [] |
| 41 | def _terminate_connections(): |
| 42 | for connection in _server_connections: |
| 43 | connection.terminate() |
| 44 | atexit.register(_terminate_connections) |
| 45 | |
| 46 | class TinfoilUIException(Exception): |
| 47 | """Exception raised when the UI returns non-zero from its main function""" |
| 48 | def __init__(self, returncode): |
| 49 | self.returncode = returncode |
| 50 | def __repr__(self): |
| 51 | return 'UI module main returned %d' % self.returncode |
| 52 | |
| 53 | class TinfoilCommandFailed(Exception): |
| 54 | """Exception raised when run_command fails""" |
| 55 | |
| 56 | class TinfoilDataStoreConnector: |
| 57 | |
| 58 | def __init__(self, tinfoil, dsindex): |
| 59 | self.tinfoil = tinfoil |
| 60 | self.dsindex = dsindex |
| 61 | def getVar(self, name): |
| 62 | value = self.tinfoil.run_command('dataStoreConnectorFindVar', self.dsindex, name) |
| 63 | overrides = None |
| 64 | if isinstance(value, dict): |
| 65 | if '_connector_origtype' in value: |
| 66 | value['_content'] = self.tinfoil._reconvert_type(value['_content'], value['_connector_origtype']) |
| 67 | del value['_connector_origtype'] |
| 68 | if '_connector_overrides' in value: |
| 69 | overrides = value['_connector_overrides'] |
| 70 | del value['_connector_overrides'] |
| 71 | return value, overrides |
| 72 | def getKeys(self): |
| 73 | return set(self.tinfoil.run_command('dataStoreConnectorGetKeys', self.dsindex)) |
| 74 | def getVarHistory(self, name): |
| 75 | return self.tinfoil.run_command('dataStoreConnectorGetVarHistory', self.dsindex, name) |
| 76 | def expandPythonRef(self, varname, expr, d): |
| 77 | ds = bb.remotedata.RemoteDatastores.transmit_datastore(d) |
| 78 | ret = self.tinfoil.run_command('dataStoreConnectorExpandPythonRef', ds, varname, expr) |
| 79 | return ret |
| 80 | def setVar(self, varname, value): |
| 81 | if self.dsindex is None: |
| 82 | self.tinfoil.run_command('setVariable', varname, value) |
| 83 | else: |
| 84 | # Not currently implemented - indicate that setting should |
| 85 | # be redirected to local side |
| 86 | return True |
| 87 | def setVarFlag(self, varname, flagname, value): |
| 88 | if self.dsindex is None: |
| 89 | self.tinfoil.run_command('dataStoreConnectorSetVarFlag', self.dsindex, varname, flagname, value) |
| 90 | else: |
| 91 | # Not currently implemented - indicate that setting should |
| 92 | # be redirected to local side |
| 93 | return True |
| 94 | def delVar(self, varname): |
| 95 | if self.dsindex is None: |
| 96 | self.tinfoil.run_command('dataStoreConnectorDelVar', self.dsindex, varname) |
| 97 | else: |
| 98 | # Not currently implemented - indicate that setting should |
| 99 | # be redirected to local side |
| 100 | return True |
| 101 | def delVarFlag(self, varname, flagname): |
| 102 | if self.dsindex is None: |
| 103 | self.tinfoil.run_command('dataStoreConnectorDelVar', self.dsindex, varname, flagname) |
| 104 | else: |
| 105 | # Not currently implemented - indicate that setting should |
| 106 | # be redirected to local side |
| 107 | return True |
| 108 | def renameVar(self, name, newname): |
| 109 | if self.dsindex is None: |
| 110 | self.tinfoil.run_command('dataStoreConnectorRenameVar', self.dsindex, name, newname) |
| 111 | else: |
| 112 | # Not currently implemented - indicate that setting should |
| 113 | # be redirected to local side |
| 114 | return True |
| 115 | |
| 116 | class TinfoilCookerAdapter: |
| 117 | """ |
| 118 | Provide an adapter for existing code that expects to access a cooker object via Tinfoil, |
| 119 | since now Tinfoil is on the client side it no longer has direct access. |
| 120 | """ |
| 121 | |
| 122 | class TinfoilCookerCollectionAdapter: |
| 123 | """ cooker.collection adapter """ |
| 124 | def __init__(self, tinfoil): |
| 125 | self.tinfoil = tinfoil |
| 126 | def get_file_appends(self, fn): |
| 127 | return self.tinfoil.get_file_appends(fn) |
| 128 | def __getattr__(self, name): |
| 129 | if name == 'overlayed': |
| 130 | return self.tinfoil.get_overlayed_recipes() |
| 131 | elif name == 'bbappends': |
| 132 | return self.tinfoil.run_command('getAllAppends') |
| 133 | else: |
| 134 | raise AttributeError("%s instance has no attribute '%s'" % (self.__class__.__name__, name)) |
| 135 | |
| 136 | class TinfoilRecipeCacheAdapter: |
| 137 | """ cooker.recipecache adapter """ |
| 138 | def __init__(self, tinfoil): |
| 139 | self.tinfoil = tinfoil |
| 140 | self._cache = {} |
| 141 | |
| 142 | def get_pkg_pn_fn(self): |
| 143 | pkg_pn = defaultdict(list, self.tinfoil.run_command('getRecipes') or []) |
| 144 | pkg_fn = {} |
| 145 | for pn, fnlist in pkg_pn.items(): |
| 146 | for fn in fnlist: |
| 147 | pkg_fn[fn] = pn |
| 148 | self._cache['pkg_pn'] = pkg_pn |
| 149 | self._cache['pkg_fn'] = pkg_fn |
| 150 | |
| 151 | def __getattr__(self, name): |
| 152 | # Grab these only when they are requested since they aren't always used |
| 153 | if name in self._cache: |
| 154 | return self._cache[name] |
| 155 | elif name == 'pkg_pn': |
| 156 | self.get_pkg_pn_fn() |
| 157 | return self._cache[name] |
| 158 | elif name == 'pkg_fn': |
| 159 | self.get_pkg_pn_fn() |
| 160 | return self._cache[name] |
| 161 | elif name == 'deps': |
| 162 | attrvalue = defaultdict(list, self.tinfoil.run_command('getRecipeDepends') or []) |
| 163 | elif name == 'rundeps': |
| 164 | attrvalue = defaultdict(lambda: defaultdict(list), self.tinfoil.run_command('getRuntimeDepends') or []) |
| 165 | elif name == 'runrecs': |
| 166 | attrvalue = defaultdict(lambda: defaultdict(list), self.tinfoil.run_command('getRuntimeRecommends') or []) |
| 167 | elif name == 'pkg_pepvpr': |
| 168 | attrvalue = self.tinfoil.run_command('getRecipeVersions') or {} |
| 169 | elif name == 'inherits': |
| 170 | attrvalue = self.tinfoil.run_command('getRecipeInherits') or {} |
| 171 | elif name == 'bbfile_priority': |
| 172 | attrvalue = self.tinfoil.run_command('getBbFilePriority') or {} |
| 173 | elif name == 'pkg_dp': |
| 174 | attrvalue = self.tinfoil.run_command('getDefaultPreference') or {} |
| 175 | else: |
| 176 | raise AttributeError("%s instance has no attribute '%s'" % (self.__class__.__name__, name)) |
| 177 | |
| 178 | self._cache[name] = attrvalue |
| 179 | return attrvalue |
| 180 | |
| 181 | def __init__(self, tinfoil): |
| 182 | self.tinfoil = tinfoil |
| 183 | self.collection = self.TinfoilCookerCollectionAdapter(tinfoil) |
| 184 | self.recipecaches = {} |
| 185 | # FIXME all machines |
| 186 | self.recipecaches[''] = self.TinfoilRecipeCacheAdapter(tinfoil) |
| 187 | self._cache = {} |
| 188 | def __getattr__(self, name): |
| 189 | # Grab these only when they are requested since they aren't always used |
| 190 | if name in self._cache: |
| 191 | return self._cache[name] |
| 192 | elif name == 'skiplist': |
| 193 | attrvalue = self.tinfoil.get_skipped_recipes() |
| 194 | elif name == 'bbfile_config_priorities': |
| 195 | ret = self.tinfoil.run_command('getLayerPriorities') |
| 196 | bbfile_config_priorities = [] |
| 197 | for collection, pattern, regex, pri in ret: |
| 198 | bbfile_config_priorities.append((collection, pattern, re.compile(regex), pri)) |
| 199 | |
| 200 | attrvalue = bbfile_config_priorities |
| 201 | else: |
| 202 | raise AttributeError("%s instance has no attribute '%s'" % (self.__class__.__name__, name)) |
| 203 | |
| 204 | self._cache[name] = attrvalue |
| 205 | return attrvalue |
| 206 | |
| 207 | def findBestProvider(self, pn): |
| 208 | return self.tinfoil.find_best_provider(pn) |
| 209 | |
| 210 | |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 211 | class Tinfoil: |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 212 | |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 213 | def __init__(self, output=sys.stdout, tracking=False, setup_logging=True): |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 214 | self.logger = logging.getLogger('BitBake') |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 215 | self.config_data = None |
| 216 | self.cooker = None |
| 217 | self.tracking = tracking |
| 218 | self.ui_module = None |
| 219 | self.server_connection = None |
| 220 | if setup_logging: |
| 221 | # This is the *client-side* logger, nothing to do with |
| 222 | # logging messages from the server |
| 223 | bb.msg.logger_create('BitBake', output) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 224 | |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 225 | def __enter__(self): |
| 226 | return self |
| 227 | |
| 228 | def __exit__(self, type, value, traceback): |
| 229 | self.shutdown() |
| 230 | |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 231 | def prepare(self, config_only=False, config_params=None, quiet=0): |
| 232 | if self.tracking: |
| 233 | extrafeatures = [bb.cooker.CookerFeatures.BASEDATASTORE_TRACKING] |
| 234 | else: |
| 235 | extrafeatures = [] |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 236 | |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 237 | if not config_params: |
| 238 | config_params = TinfoilConfigParameters(config_only=config_only, quiet=quiet) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 239 | |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 240 | cookerconfig = CookerConfiguration() |
| 241 | cookerconfig.setConfigParameters(config_params) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 242 | |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 243 | server, self.server_connection, ui_module = setup_bitbake(config_params, |
| 244 | cookerconfig, |
| 245 | extrafeatures) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 246 | |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 247 | self.ui_module = ui_module |
| 248 | |
| 249 | # Ensure the path to bitbake's bin directory is in PATH so that things like |
| 250 | # bitbake-worker can be run (usually this is the case, but it doesn't have to be) |
| 251 | path = os.getenv('PATH').split(':') |
| 252 | bitbakebinpath = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'bin')) |
| 253 | for entry in path: |
| 254 | if entry.endswith(os.sep): |
| 255 | entry = entry[:-1] |
| 256 | if os.path.abspath(entry) == bitbakebinpath: |
| 257 | break |
| 258 | else: |
| 259 | path.insert(0, bitbakebinpath) |
| 260 | os.environ['PATH'] = ':'.join(path) |
| 261 | |
| 262 | if self.server_connection: |
| 263 | _server_connections.append(self.server_connection) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 264 | if config_only: |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 265 | config_params.updateToServer(self.server_connection.connection, os.environ.copy()) |
| 266 | self.run_command('parseConfiguration') |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 267 | else: |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 268 | self.run_actions(config_params) |
| 269 | |
| 270 | self.config_data = bb.data.init() |
| 271 | connector = TinfoilDataStoreConnector(self, None) |
| 272 | self.config_data.setVar('_remote_data', connector) |
| 273 | self.cooker = TinfoilCookerAdapter(self) |
| 274 | self.cooker_data = self.cooker.recipecaches[''] |
| 275 | else: |
| 276 | raise Exception('Failed to start bitbake server') |
| 277 | |
| 278 | def run_actions(self, config_params): |
| 279 | """ |
| 280 | Run the actions specified in config_params through the UI. |
| 281 | """ |
| 282 | ret = self.ui_module.main(self.server_connection.connection, self.server_connection.events, config_params) |
| 283 | if ret: |
| 284 | raise TinfoilUIException(ret) |
| 285 | |
| 286 | def parseRecipes(self): |
| 287 | """ |
| 288 | Force a parse of all recipes. Normally you should specify |
| 289 | config_only=False when calling prepare() instead of using this |
| 290 | function; this function is designed for situations where you need |
| 291 | to initialise Tinfoil and use it with config_only=True first and |
| 292 | then conditionally call this function to parse recipes later. |
| 293 | """ |
| 294 | config_params = TinfoilConfigParameters(config_only=False) |
| 295 | self.run_actions(config_params) |
| 296 | |
| 297 | def run_command(self, command, *params): |
| 298 | """ |
| 299 | Run a command on the server (as implemented in bb.command). |
| 300 | Note that there are two types of command - synchronous and |
| 301 | asynchronous; in order to receive the results of asynchronous |
| 302 | commands you will need to set an appropriate event mask |
| 303 | using set_event_mask() and listen for the result using |
| 304 | wait_event() - with the correct event mask you'll at least get |
| 305 | bb.command.CommandCompleted and possibly other events before |
| 306 | that depending on the command. |
| 307 | """ |
| 308 | if not self.server_connection: |
| 309 | raise Exception('Not connected to server (did you call .prepare()?)') |
| 310 | |
| 311 | commandline = [command] |
| 312 | if params: |
| 313 | commandline.extend(params) |
| 314 | result = self.server_connection.connection.runCommand(commandline) |
| 315 | if result[1]: |
| 316 | raise TinfoilCommandFailed(result[1]) |
| 317 | return result[0] |
| 318 | |
| 319 | def set_event_mask(self, eventlist): |
| 320 | """Set the event mask which will be applied within wait_event()""" |
| 321 | if not self.server_connection: |
| 322 | raise Exception('Not connected to server (did you call .prepare()?)') |
| 323 | llevel, debug_domains = bb.msg.constructLogOptions() |
| 324 | ret = self.run_command('setEventMask', self.server_connection.connection.getEventHandle(), llevel, debug_domains, eventlist) |
| 325 | if not ret: |
| 326 | raise Exception('setEventMask failed') |
| 327 | |
| 328 | def wait_event(self, timeout=0): |
| 329 | """ |
| 330 | Wait for an event from the server for the specified time. |
| 331 | A timeout of 0 means don't wait if there are no events in the queue. |
| 332 | Returns the next event in the queue or None if the timeout was |
| 333 | reached. Note that in order to recieve any events you will |
| 334 | first need to set the internal event mask using set_event_mask() |
| 335 | (otherwise whatever event mask the UI set up will be in effect). |
| 336 | """ |
| 337 | if not self.server_connection: |
| 338 | raise Exception('Not connected to server (did you call .prepare()?)') |
| 339 | return self.server_connection.events.waitEvent(timeout) |
| 340 | |
| 341 | def get_overlayed_recipes(self): |
| 342 | return defaultdict(list, self.run_command('getOverlayedRecipes')) |
| 343 | |
| 344 | def get_skipped_recipes(self): |
| 345 | return OrderedDict(self.run_command('getSkippedRecipes')) |
| 346 | |
| 347 | def get_all_providers(self): |
| 348 | return defaultdict(list, self.run_command('allProviders')) |
| 349 | |
| 350 | def find_providers(self): |
| 351 | return self.run_command('findProviders') |
| 352 | |
| 353 | def find_best_provider(self, pn): |
| 354 | return self.run_command('findBestProvider', pn) |
| 355 | |
| 356 | def get_runtime_providers(self, rdep): |
| 357 | return self.run_command('getRuntimeProviders', rdep) |
| 358 | |
| 359 | def get_recipe_file(self, pn): |
| 360 | """ |
| 361 | Get the file name for the specified recipe/target. Raises |
| 362 | bb.providers.NoProvider if there is no match or the recipe was |
| 363 | skipped. |
| 364 | """ |
| 365 | best = self.find_best_provider(pn) |
| 366 | if not best or (len(best) > 3 and not best[3]): |
| 367 | skiplist = self.get_skipped_recipes() |
| 368 | taskdata = bb.taskdata.TaskData(None, skiplist=skiplist) |
| 369 | skipreasons = taskdata.get_reasons(pn) |
| 370 | if skipreasons: |
| 371 | raise bb.providers.NoProvider('%s is unavailable:\n %s' % (pn, ' \n'.join(skipreasons))) |
| 372 | else: |
| 373 | raise bb.providers.NoProvider('Unable to find any recipe file matching "%s"' % pn) |
| 374 | return best[3] |
| 375 | |
| 376 | def get_file_appends(self, fn): |
| 377 | return self.run_command('getFileAppends', fn) |
| 378 | |
| 379 | def parse_recipe(self, pn): |
| 380 | """ |
| 381 | Parse the specified recipe and return a datastore object |
| 382 | representing the environment for the recipe. |
| 383 | """ |
| 384 | fn = self.get_recipe_file(pn) |
| 385 | return self.parse_recipe_file(fn) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 386 | |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 387 | def parse_recipe_file(self, fn, appends=True, appendlist=None, config_data=None): |
| 388 | """ |
| 389 | Parse the specified recipe file (with or without bbappends) |
| 390 | and return a datastore object representing the environment |
| 391 | for the recipe. |
| 392 | Parameters: |
| 393 | fn: recipe file to parse - can be a file path or virtual |
| 394 | specification |
| 395 | appends: True to apply bbappends, False otherwise |
| 396 | appendlist: optional list of bbappend files to apply, if you |
| 397 | want to filter them |
| 398 | config_data: custom config datastore to use. NOTE: if you |
| 399 | specify config_data then you cannot use a virtual |
| 400 | specification for fn. |
| 401 | """ |
| 402 | if appends and appendlist == []: |
| 403 | appends = False |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 404 | if config_data: |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 405 | dctr = bb.remotedata.RemoteDatastores.transmit_datastore(config_data) |
| 406 | dscon = self.run_command('parseRecipeFile', fn, appends, appendlist, dctr) |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 407 | else: |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 408 | dscon = self.run_command('parseRecipeFile', fn, appends, appendlist) |
| 409 | if dscon: |
| 410 | return self._reconvert_type(dscon, 'DataStoreConnectionHandle') |
| 411 | else: |
| 412 | return None |
| 413 | |
| 414 | def build_file(self, buildfile, task): |
| 415 | """ |
| 416 | Runs the specified task for just a single recipe (i.e. no dependencies). |
| 417 | This is equivalent to bitbake -b, except no warning will be printed. |
| 418 | """ |
| 419 | return self.run_command('buildFile', buildfile, task, True) |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 420 | |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 421 | def shutdown(self): |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 422 | if self.server_connection: |
| 423 | self.run_command('clientComplete') |
| 424 | _server_connections.remove(self.server_connection) |
| 425 | bb.event.ui_queue = [] |
| 426 | self.server_connection.terminate() |
| 427 | self.server_connection = None |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 428 | |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 429 | def _reconvert_type(self, obj, origtypename): |
| 430 | """ |
| 431 | Convert an object back to the right type, in the case |
| 432 | that marshalling has changed it (especially with xmlrpc) |
| 433 | """ |
| 434 | supported_types = { |
| 435 | 'set': set, |
| 436 | 'DataStoreConnectionHandle': bb.command.DataStoreConnectionHandle, |
| 437 | } |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 438 | |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 439 | origtype = supported_types.get(origtypename, None) |
| 440 | if origtype is None: |
| 441 | raise Exception('Unsupported type "%s"' % origtypename) |
| 442 | if type(obj) == origtype: |
| 443 | newobj = obj |
| 444 | elif isinstance(obj, dict): |
| 445 | # New style class |
| 446 | newobj = origtype() |
| 447 | for k,v in obj.items(): |
| 448 | setattr(newobj, k, v) |
| 449 | else: |
| 450 | # Assume we can coerce the type |
| 451 | newobj = origtype(obj) |
| 452 | |
| 453 | if isinstance(newobj, bb.command.DataStoreConnectionHandle): |
| 454 | connector = TinfoilDataStoreConnector(self, newobj.dsindex) |
| 455 | newobj = bb.data.init() |
| 456 | newobj.setVar('_remote_data', connector) |
| 457 | |
| 458 | return newobj |
| 459 | |
| 460 | |
| 461 | class TinfoilConfigParameters(BitBakeConfigParameters): |
| 462 | |
| 463 | def __init__(self, config_only, **options): |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 464 | self.initial_options = options |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 465 | # Apply some sane defaults |
| 466 | if not 'parse_only' in options: |
| 467 | self.initial_options['parse_only'] = not config_only |
| 468 | #if not 'status_only' in options: |
| 469 | # self.initial_options['status_only'] = config_only |
| 470 | if not 'ui' in options: |
| 471 | self.initial_options['ui'] = 'knotty' |
| 472 | if not 'argv' in options: |
| 473 | self.initial_options['argv'] = [] |
| 474 | |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 475 | super(TinfoilConfigParameters, self).__init__() |
| 476 | |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 477 | def parseCommandLine(self, argv=None): |
| 478 | # We don't want any parameters parsed from the command line |
| 479 | opts = super(TinfoilConfigParameters, self).parseCommandLine([]) |
| 480 | for key, val in self.initial_options.items(): |
| 481 | setattr(opts[0], key, val) |
| 482 | return opts |