1*4882a593Smuzhiyun# tinfoil: a simple wrapper around cooker for bitbake-based command-line utilities 2*4882a593Smuzhiyun# 3*4882a593Smuzhiyun# Copyright (C) 2012-2017 Intel Corporation 4*4882a593Smuzhiyun# Copyright (C) 2011 Mentor Graphics Corporation 5*4882a593Smuzhiyun# Copyright (C) 2006-2012 Richard Purdie 6*4882a593Smuzhiyun# 7*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 8*4882a593Smuzhiyun# 9*4882a593Smuzhiyun 10*4882a593Smuzhiyunimport logging 11*4882a593Smuzhiyunimport os 12*4882a593Smuzhiyunimport sys 13*4882a593Smuzhiyunimport atexit 14*4882a593Smuzhiyunimport re 15*4882a593Smuzhiyunfrom collections import OrderedDict, defaultdict 16*4882a593Smuzhiyunfrom functools import partial 17*4882a593Smuzhiyun 18*4882a593Smuzhiyunimport bb.cache 19*4882a593Smuzhiyunimport bb.cooker 20*4882a593Smuzhiyunimport bb.providers 21*4882a593Smuzhiyunimport bb.taskdata 22*4882a593Smuzhiyunimport bb.utils 23*4882a593Smuzhiyunimport bb.command 24*4882a593Smuzhiyunimport bb.remotedata 25*4882a593Smuzhiyunfrom bb.main import setup_bitbake, BitBakeConfigParameters 26*4882a593Smuzhiyunimport bb.fetch2 27*4882a593Smuzhiyun 28*4882a593Smuzhiyun 29*4882a593Smuzhiyun# We need this in order to shut down the connection to the bitbake server, 30*4882a593Smuzhiyun# otherwise the process will never properly exit 31*4882a593Smuzhiyun_server_connections = [] 32*4882a593Smuzhiyundef _terminate_connections(): 33*4882a593Smuzhiyun for connection in _server_connections: 34*4882a593Smuzhiyun connection.terminate() 35*4882a593Smuzhiyunatexit.register(_terminate_connections) 36*4882a593Smuzhiyun 37*4882a593Smuzhiyunclass TinfoilUIException(Exception): 38*4882a593Smuzhiyun """Exception raised when the UI returns non-zero from its main function""" 39*4882a593Smuzhiyun def __init__(self, returncode): 40*4882a593Smuzhiyun self.returncode = returncode 41*4882a593Smuzhiyun def __repr__(self): 42*4882a593Smuzhiyun return 'UI module main returned %d' % self.returncode 43*4882a593Smuzhiyun 44*4882a593Smuzhiyunclass TinfoilCommandFailed(Exception): 45*4882a593Smuzhiyun """Exception raised when run_command fails""" 46*4882a593Smuzhiyun 47*4882a593Smuzhiyunclass TinfoilDataStoreConnectorVarHistory: 48*4882a593Smuzhiyun def __init__(self, tinfoil, dsindex): 49*4882a593Smuzhiyun self.tinfoil = tinfoil 50*4882a593Smuzhiyun self.dsindex = dsindex 51*4882a593Smuzhiyun 52*4882a593Smuzhiyun def remoteCommand(self, cmd, *args, **kwargs): 53*4882a593Smuzhiyun return self.tinfoil.run_command('dataStoreConnectorVarHistCmd', self.dsindex, cmd, args, kwargs) 54*4882a593Smuzhiyun 55*4882a593Smuzhiyun def emit(self, var, oval, val, o, d): 56*4882a593Smuzhiyun ret = self.tinfoil.run_command('dataStoreConnectorVarHistCmdEmit', self.dsindex, var, oval, val, d.dsindex) 57*4882a593Smuzhiyun o.write(ret) 58*4882a593Smuzhiyun 59*4882a593Smuzhiyun def __getattr__(self, name): 60*4882a593Smuzhiyun if not hasattr(bb.data_smart.VariableHistory, name): 61*4882a593Smuzhiyun raise AttributeError("VariableHistory has no such method %s" % name) 62*4882a593Smuzhiyun 63*4882a593Smuzhiyun newfunc = partial(self.remoteCommand, name) 64*4882a593Smuzhiyun setattr(self, name, newfunc) 65*4882a593Smuzhiyun return newfunc 66*4882a593Smuzhiyun 67*4882a593Smuzhiyunclass TinfoilDataStoreConnectorIncHistory: 68*4882a593Smuzhiyun def __init__(self, tinfoil, dsindex): 69*4882a593Smuzhiyun self.tinfoil = tinfoil 70*4882a593Smuzhiyun self.dsindex = dsindex 71*4882a593Smuzhiyun 72*4882a593Smuzhiyun def remoteCommand(self, cmd, *args, **kwargs): 73*4882a593Smuzhiyun return self.tinfoil.run_command('dataStoreConnectorIncHistCmd', self.dsindex, cmd, args, kwargs) 74*4882a593Smuzhiyun 75*4882a593Smuzhiyun def __getattr__(self, name): 76*4882a593Smuzhiyun if not hasattr(bb.data_smart.IncludeHistory, name): 77*4882a593Smuzhiyun raise AttributeError("IncludeHistory has no such method %s" % name) 78*4882a593Smuzhiyun 79*4882a593Smuzhiyun newfunc = partial(self.remoteCommand, name) 80*4882a593Smuzhiyun setattr(self, name, newfunc) 81*4882a593Smuzhiyun return newfunc 82*4882a593Smuzhiyun 83*4882a593Smuzhiyunclass TinfoilDataStoreConnector: 84*4882a593Smuzhiyun """ 85*4882a593Smuzhiyun Connector object used to enable access to datastore objects via tinfoil 86*4882a593Smuzhiyun Method calls are transmitted to the remote datastore for processing, if a datastore is 87*4882a593Smuzhiyun returned we return a connector object for the new store 88*4882a593Smuzhiyun """ 89*4882a593Smuzhiyun 90*4882a593Smuzhiyun def __init__(self, tinfoil, dsindex): 91*4882a593Smuzhiyun self.tinfoil = tinfoil 92*4882a593Smuzhiyun self.dsindex = dsindex 93*4882a593Smuzhiyun self.varhistory = TinfoilDataStoreConnectorVarHistory(tinfoil, dsindex) 94*4882a593Smuzhiyun self.inchistory = TinfoilDataStoreConnectorIncHistory(tinfoil, dsindex) 95*4882a593Smuzhiyun 96*4882a593Smuzhiyun def remoteCommand(self, cmd, *args, **kwargs): 97*4882a593Smuzhiyun ret = self.tinfoil.run_command('dataStoreConnectorCmd', self.dsindex, cmd, args, kwargs) 98*4882a593Smuzhiyun if isinstance(ret, bb.command.DataStoreConnectionHandle): 99*4882a593Smuzhiyun return TinfoilDataStoreConnector(self.tinfoil, ret.dsindex) 100*4882a593Smuzhiyun return ret 101*4882a593Smuzhiyun 102*4882a593Smuzhiyun def __getattr__(self, name): 103*4882a593Smuzhiyun if not hasattr(bb.data._dict_type, name): 104*4882a593Smuzhiyun raise AttributeError("Data store has no such method %s" % name) 105*4882a593Smuzhiyun 106*4882a593Smuzhiyun newfunc = partial(self.remoteCommand, name) 107*4882a593Smuzhiyun setattr(self, name, newfunc) 108*4882a593Smuzhiyun return newfunc 109*4882a593Smuzhiyun 110*4882a593Smuzhiyun def __iter__(self): 111*4882a593Smuzhiyun keys = self.tinfoil.run_command('dataStoreConnectorCmd', self.dsindex, "keys", [], {}) 112*4882a593Smuzhiyun for k in keys: 113*4882a593Smuzhiyun yield k 114*4882a593Smuzhiyun 115*4882a593Smuzhiyunclass TinfoilCookerAdapter: 116*4882a593Smuzhiyun """ 117*4882a593Smuzhiyun Provide an adapter for existing code that expects to access a cooker object via Tinfoil, 118*4882a593Smuzhiyun since now Tinfoil is on the client side it no longer has direct access. 119*4882a593Smuzhiyun """ 120*4882a593Smuzhiyun 121*4882a593Smuzhiyun class TinfoilCookerCollectionAdapter: 122*4882a593Smuzhiyun """ cooker.collection adapter """ 123*4882a593Smuzhiyun def __init__(self, tinfoil, mc=''): 124*4882a593Smuzhiyun self.tinfoil = tinfoil 125*4882a593Smuzhiyun self.mc = mc 126*4882a593Smuzhiyun def get_file_appends(self, fn): 127*4882a593Smuzhiyun return self.tinfoil.get_file_appends(fn, self.mc) 128*4882a593Smuzhiyun def __getattr__(self, name): 129*4882a593Smuzhiyun if name == 'overlayed': 130*4882a593Smuzhiyun return self.tinfoil.get_overlayed_recipes(self.mc) 131*4882a593Smuzhiyun elif name == 'bbappends': 132*4882a593Smuzhiyun return self.tinfoil.run_command('getAllAppends', self.mc) 133*4882a593Smuzhiyun else: 134*4882a593Smuzhiyun raise AttributeError("%s instance has no attribute '%s'" % (self.__class__.__name__, name)) 135*4882a593Smuzhiyun 136*4882a593Smuzhiyun class TinfoilRecipeCacheAdapter: 137*4882a593Smuzhiyun """ cooker.recipecache adapter """ 138*4882a593Smuzhiyun def __init__(self, tinfoil, mc=''): 139*4882a593Smuzhiyun self.tinfoil = tinfoil 140*4882a593Smuzhiyun self.mc = mc 141*4882a593Smuzhiyun self._cache = {} 142*4882a593Smuzhiyun 143*4882a593Smuzhiyun def get_pkg_pn_fn(self): 144*4882a593Smuzhiyun pkg_pn = defaultdict(list, self.tinfoil.run_command('getRecipes', self.mc) or []) 145*4882a593Smuzhiyun pkg_fn = {} 146*4882a593Smuzhiyun for pn, fnlist in pkg_pn.items(): 147*4882a593Smuzhiyun for fn in fnlist: 148*4882a593Smuzhiyun pkg_fn[fn] = pn 149*4882a593Smuzhiyun self._cache['pkg_pn'] = pkg_pn 150*4882a593Smuzhiyun self._cache['pkg_fn'] = pkg_fn 151*4882a593Smuzhiyun 152*4882a593Smuzhiyun def __getattr__(self, name): 153*4882a593Smuzhiyun # Grab these only when they are requested since they aren't always used 154*4882a593Smuzhiyun if name in self._cache: 155*4882a593Smuzhiyun return self._cache[name] 156*4882a593Smuzhiyun elif name == 'pkg_pn': 157*4882a593Smuzhiyun self.get_pkg_pn_fn() 158*4882a593Smuzhiyun return self._cache[name] 159*4882a593Smuzhiyun elif name == 'pkg_fn': 160*4882a593Smuzhiyun self.get_pkg_pn_fn() 161*4882a593Smuzhiyun return self._cache[name] 162*4882a593Smuzhiyun elif name == 'deps': 163*4882a593Smuzhiyun attrvalue = defaultdict(list, self.tinfoil.run_command('getRecipeDepends', self.mc) or []) 164*4882a593Smuzhiyun elif name == 'rundeps': 165*4882a593Smuzhiyun attrvalue = defaultdict(lambda: defaultdict(list), self.tinfoil.run_command('getRuntimeDepends', self.mc) or []) 166*4882a593Smuzhiyun elif name == 'runrecs': 167*4882a593Smuzhiyun attrvalue = defaultdict(lambda: defaultdict(list), self.tinfoil.run_command('getRuntimeRecommends', self.mc) or []) 168*4882a593Smuzhiyun elif name == 'pkg_pepvpr': 169*4882a593Smuzhiyun attrvalue = self.tinfoil.run_command('getRecipeVersions', self.mc) or {} 170*4882a593Smuzhiyun elif name == 'inherits': 171*4882a593Smuzhiyun attrvalue = self.tinfoil.run_command('getRecipeInherits', self.mc) or {} 172*4882a593Smuzhiyun elif name == 'bbfile_priority': 173*4882a593Smuzhiyun attrvalue = self.tinfoil.run_command('getBbFilePriority', self.mc) or {} 174*4882a593Smuzhiyun elif name == 'pkg_dp': 175*4882a593Smuzhiyun attrvalue = self.tinfoil.run_command('getDefaultPreference', self.mc) or {} 176*4882a593Smuzhiyun elif name == 'fn_provides': 177*4882a593Smuzhiyun attrvalue = self.tinfoil.run_command('getRecipeProvides', self.mc) or {} 178*4882a593Smuzhiyun elif name == 'packages': 179*4882a593Smuzhiyun attrvalue = self.tinfoil.run_command('getRecipePackages', self.mc) or {} 180*4882a593Smuzhiyun elif name == 'packages_dynamic': 181*4882a593Smuzhiyun attrvalue = self.tinfoil.run_command('getRecipePackagesDynamic', self.mc) or {} 182*4882a593Smuzhiyun elif name == 'rproviders': 183*4882a593Smuzhiyun attrvalue = self.tinfoil.run_command('getRProviders', self.mc) or {} 184*4882a593Smuzhiyun else: 185*4882a593Smuzhiyun raise AttributeError("%s instance has no attribute '%s'" % (self.__class__.__name__, name)) 186*4882a593Smuzhiyun 187*4882a593Smuzhiyun self._cache[name] = attrvalue 188*4882a593Smuzhiyun return attrvalue 189*4882a593Smuzhiyun 190*4882a593Smuzhiyun def __init__(self, tinfoil): 191*4882a593Smuzhiyun self.tinfoil = tinfoil 192*4882a593Smuzhiyun self.multiconfigs = [''] + (tinfoil.config_data.getVar('BBMULTICONFIG') or '').split() 193*4882a593Smuzhiyun self.collections = {} 194*4882a593Smuzhiyun self.recipecaches = {} 195*4882a593Smuzhiyun for mc in self.multiconfigs: 196*4882a593Smuzhiyun self.collections[mc] = self.TinfoilCookerCollectionAdapter(tinfoil, mc) 197*4882a593Smuzhiyun self.recipecaches[mc] = self.TinfoilRecipeCacheAdapter(tinfoil, mc) 198*4882a593Smuzhiyun self._cache = {} 199*4882a593Smuzhiyun def __getattr__(self, name): 200*4882a593Smuzhiyun # Grab these only when they are requested since they aren't always used 201*4882a593Smuzhiyun if name in self._cache: 202*4882a593Smuzhiyun return self._cache[name] 203*4882a593Smuzhiyun elif name == 'skiplist': 204*4882a593Smuzhiyun attrvalue = self.tinfoil.get_skipped_recipes() 205*4882a593Smuzhiyun elif name == 'bbfile_config_priorities': 206*4882a593Smuzhiyun ret = self.tinfoil.run_command('getLayerPriorities') 207*4882a593Smuzhiyun bbfile_config_priorities = [] 208*4882a593Smuzhiyun for collection, pattern, regex, pri in ret: 209*4882a593Smuzhiyun bbfile_config_priorities.append((collection, pattern, re.compile(regex), pri)) 210*4882a593Smuzhiyun 211*4882a593Smuzhiyun attrvalue = bbfile_config_priorities 212*4882a593Smuzhiyun else: 213*4882a593Smuzhiyun raise AttributeError("%s instance has no attribute '%s'" % (self.__class__.__name__, name)) 214*4882a593Smuzhiyun 215*4882a593Smuzhiyun self._cache[name] = attrvalue 216*4882a593Smuzhiyun return attrvalue 217*4882a593Smuzhiyun 218*4882a593Smuzhiyun def findBestProvider(self, pn): 219*4882a593Smuzhiyun return self.tinfoil.find_best_provider(pn) 220*4882a593Smuzhiyun 221*4882a593Smuzhiyun 222*4882a593Smuzhiyunclass TinfoilRecipeInfo: 223*4882a593Smuzhiyun """ 224*4882a593Smuzhiyun Provides a convenient representation of the cached information for a single recipe. 225*4882a593Smuzhiyun Some attributes are set on construction, others are read on-demand (which internally 226*4882a593Smuzhiyun may result in a remote procedure call to the bitbake server the first time). 227*4882a593Smuzhiyun Note that only information which is cached is available through this object - if 228*4882a593Smuzhiyun you need other variable values you will need to parse the recipe using 229*4882a593Smuzhiyun Tinfoil.parse_recipe(). 230*4882a593Smuzhiyun """ 231*4882a593Smuzhiyun def __init__(self, recipecache, d, pn, fn, fns): 232*4882a593Smuzhiyun self._recipecache = recipecache 233*4882a593Smuzhiyun self._d = d 234*4882a593Smuzhiyun self.pn = pn 235*4882a593Smuzhiyun self.fn = fn 236*4882a593Smuzhiyun self.fns = fns 237*4882a593Smuzhiyun self.inherit_files = recipecache.inherits[fn] 238*4882a593Smuzhiyun self.depends = recipecache.deps[fn] 239*4882a593Smuzhiyun (self.pe, self.pv, self.pr) = recipecache.pkg_pepvpr[fn] 240*4882a593Smuzhiyun self._cached_packages = None 241*4882a593Smuzhiyun self._cached_rprovides = None 242*4882a593Smuzhiyun self._cached_packages_dynamic = None 243*4882a593Smuzhiyun 244*4882a593Smuzhiyun def __getattr__(self, name): 245*4882a593Smuzhiyun if name == 'alternates': 246*4882a593Smuzhiyun return [x for x in self.fns if x != self.fn] 247*4882a593Smuzhiyun elif name == 'rdepends': 248*4882a593Smuzhiyun return self._recipecache.rundeps[self.fn] 249*4882a593Smuzhiyun elif name == 'rrecommends': 250*4882a593Smuzhiyun return self._recipecache.runrecs[self.fn] 251*4882a593Smuzhiyun elif name == 'provides': 252*4882a593Smuzhiyun return self._recipecache.fn_provides[self.fn] 253*4882a593Smuzhiyun elif name == 'packages': 254*4882a593Smuzhiyun if self._cached_packages is None: 255*4882a593Smuzhiyun self._cached_packages = [] 256*4882a593Smuzhiyun for pkg, fns in self._recipecache.packages.items(): 257*4882a593Smuzhiyun if self.fn in fns: 258*4882a593Smuzhiyun self._cached_packages.append(pkg) 259*4882a593Smuzhiyun return self._cached_packages 260*4882a593Smuzhiyun elif name == 'packages_dynamic': 261*4882a593Smuzhiyun if self._cached_packages_dynamic is None: 262*4882a593Smuzhiyun self._cached_packages_dynamic = [] 263*4882a593Smuzhiyun for pkg, fns in self._recipecache.packages_dynamic.items(): 264*4882a593Smuzhiyun if self.fn in fns: 265*4882a593Smuzhiyun self._cached_packages_dynamic.append(pkg) 266*4882a593Smuzhiyun return self._cached_packages_dynamic 267*4882a593Smuzhiyun elif name == 'rprovides': 268*4882a593Smuzhiyun if self._cached_rprovides is None: 269*4882a593Smuzhiyun self._cached_rprovides = [] 270*4882a593Smuzhiyun for pkg, fns in self._recipecache.rproviders.items(): 271*4882a593Smuzhiyun if self.fn in fns: 272*4882a593Smuzhiyun self._cached_rprovides.append(pkg) 273*4882a593Smuzhiyun return self._cached_rprovides 274*4882a593Smuzhiyun else: 275*4882a593Smuzhiyun raise AttributeError("%s instance has no attribute '%s'" % (self.__class__.__name__, name)) 276*4882a593Smuzhiyun def inherits(self, only_recipe=False): 277*4882a593Smuzhiyun """ 278*4882a593Smuzhiyun Get the inherited classes for a recipe. Returns the class names only. 279*4882a593Smuzhiyun Parameters: 280*4882a593Smuzhiyun only_recipe: True to return only the classes inherited by the recipe 281*4882a593Smuzhiyun itself, False to return all classes inherited within 282*4882a593Smuzhiyun the context for the recipe (which includes globally 283*4882a593Smuzhiyun inherited classes). 284*4882a593Smuzhiyun """ 285*4882a593Smuzhiyun if only_recipe: 286*4882a593Smuzhiyun global_inherit = [x for x in (self._d.getVar('BBINCLUDED') or '').split() if x.endswith('.bbclass')] 287*4882a593Smuzhiyun else: 288*4882a593Smuzhiyun global_inherit = [] 289*4882a593Smuzhiyun for clsfile in self.inherit_files: 290*4882a593Smuzhiyun if only_recipe and clsfile in global_inherit: 291*4882a593Smuzhiyun continue 292*4882a593Smuzhiyun clsname = os.path.splitext(os.path.basename(clsfile))[0] 293*4882a593Smuzhiyun yield clsname 294*4882a593Smuzhiyun def __str__(self): 295*4882a593Smuzhiyun return '%s' % self.pn 296*4882a593Smuzhiyun 297*4882a593Smuzhiyun 298*4882a593Smuzhiyunclass Tinfoil: 299*4882a593Smuzhiyun """ 300*4882a593Smuzhiyun Tinfoil - an API for scripts and utilities to query 301*4882a593Smuzhiyun BitBake internals and perform build operations. 302*4882a593Smuzhiyun """ 303*4882a593Smuzhiyun 304*4882a593Smuzhiyun def __init__(self, output=sys.stdout, tracking=False, setup_logging=True): 305*4882a593Smuzhiyun """ 306*4882a593Smuzhiyun Create a new tinfoil object. 307*4882a593Smuzhiyun Parameters: 308*4882a593Smuzhiyun output: specifies where console output should be sent. Defaults 309*4882a593Smuzhiyun to sys.stdout. 310*4882a593Smuzhiyun tracking: True to enable variable history tracking, False to 311*4882a593Smuzhiyun disable it (default). Enabling this has a minor 312*4882a593Smuzhiyun performance impact so typically it isn't enabled 313*4882a593Smuzhiyun unless you need to query variable history. 314*4882a593Smuzhiyun setup_logging: True to setup a logger so that things like 315*4882a593Smuzhiyun bb.warn() will work immediately and timeout warnings 316*4882a593Smuzhiyun are visible; False to let BitBake do this itself. 317*4882a593Smuzhiyun """ 318*4882a593Smuzhiyun self.logger = logging.getLogger('BitBake') 319*4882a593Smuzhiyun self.config_data = None 320*4882a593Smuzhiyun self.cooker = None 321*4882a593Smuzhiyun self.tracking = tracking 322*4882a593Smuzhiyun self.ui_module = None 323*4882a593Smuzhiyun self.server_connection = None 324*4882a593Smuzhiyun self.recipes_parsed = False 325*4882a593Smuzhiyun self.quiet = 0 326*4882a593Smuzhiyun self.oldhandlers = self.logger.handlers[:] 327*4882a593Smuzhiyun if setup_logging: 328*4882a593Smuzhiyun # This is the *client-side* logger, nothing to do with 329*4882a593Smuzhiyun # logging messages from the server 330*4882a593Smuzhiyun bb.msg.logger_create('BitBake', output) 331*4882a593Smuzhiyun self.localhandlers = [] 332*4882a593Smuzhiyun for handler in self.logger.handlers: 333*4882a593Smuzhiyun if handler not in self.oldhandlers: 334*4882a593Smuzhiyun self.localhandlers.append(handler) 335*4882a593Smuzhiyun 336*4882a593Smuzhiyun def __enter__(self): 337*4882a593Smuzhiyun return self 338*4882a593Smuzhiyun 339*4882a593Smuzhiyun def __exit__(self, type, value, traceback): 340*4882a593Smuzhiyun self.shutdown() 341*4882a593Smuzhiyun 342*4882a593Smuzhiyun def prepare(self, config_only=False, config_params=None, quiet=0, extra_features=None): 343*4882a593Smuzhiyun """ 344*4882a593Smuzhiyun Prepares the underlying BitBake system to be used via tinfoil. 345*4882a593Smuzhiyun This function must be called prior to calling any of the other 346*4882a593Smuzhiyun functions in the API. 347*4882a593Smuzhiyun NOTE: if you call prepare() you must absolutely call shutdown() 348*4882a593Smuzhiyun before your code terminates. You can use a "with" block to ensure 349*4882a593Smuzhiyun this happens e.g. 350*4882a593Smuzhiyun 351*4882a593Smuzhiyun with bb.tinfoil.Tinfoil() as tinfoil: 352*4882a593Smuzhiyun tinfoil.prepare() 353*4882a593Smuzhiyun ... 354*4882a593Smuzhiyun 355*4882a593Smuzhiyun Parameters: 356*4882a593Smuzhiyun config_only: True to read only the configuration and not load 357*4882a593Smuzhiyun the cache / parse recipes. This is useful if you just 358*4882a593Smuzhiyun want to query the value of a variable at the global 359*4882a593Smuzhiyun level or you want to do anything else that doesn't 360*4882a593Smuzhiyun involve knowing anything about the recipes in the 361*4882a593Smuzhiyun current configuration. False loads the cache / parses 362*4882a593Smuzhiyun recipes. 363*4882a593Smuzhiyun config_params: optionally specify your own configuration 364*4882a593Smuzhiyun parameters. If not specified an instance of 365*4882a593Smuzhiyun TinfoilConfigParameters will be created internally. 366*4882a593Smuzhiyun quiet: quiet level controlling console output - equivalent 367*4882a593Smuzhiyun to bitbake's -q/--quiet option. Default of 0 gives 368*4882a593Smuzhiyun the same output level as normal bitbake execution. 369*4882a593Smuzhiyun extra_features: extra features to be added to the feature 370*4882a593Smuzhiyun set requested from the server. See 371*4882a593Smuzhiyun CookerFeatures._feature_list for possible 372*4882a593Smuzhiyun features. 373*4882a593Smuzhiyun """ 374*4882a593Smuzhiyun self.quiet = quiet 375*4882a593Smuzhiyun 376*4882a593Smuzhiyun if self.tracking: 377*4882a593Smuzhiyun extrafeatures = [bb.cooker.CookerFeatures.BASEDATASTORE_TRACKING] 378*4882a593Smuzhiyun else: 379*4882a593Smuzhiyun extrafeatures = [] 380*4882a593Smuzhiyun 381*4882a593Smuzhiyun if extra_features: 382*4882a593Smuzhiyun extrafeatures += extra_features 383*4882a593Smuzhiyun 384*4882a593Smuzhiyun if not config_params: 385*4882a593Smuzhiyun config_params = TinfoilConfigParameters(config_only=config_only, quiet=quiet) 386*4882a593Smuzhiyun 387*4882a593Smuzhiyun if not config_only: 388*4882a593Smuzhiyun # Disable local loggers because the UI module is going to set up its own 389*4882a593Smuzhiyun for handler in self.localhandlers: 390*4882a593Smuzhiyun self.logger.handlers.remove(handler) 391*4882a593Smuzhiyun self.localhandlers = [] 392*4882a593Smuzhiyun 393*4882a593Smuzhiyun self.server_connection, ui_module = setup_bitbake(config_params, extrafeatures) 394*4882a593Smuzhiyun 395*4882a593Smuzhiyun self.ui_module = ui_module 396*4882a593Smuzhiyun 397*4882a593Smuzhiyun # Ensure the path to bitbake's bin directory is in PATH so that things like 398*4882a593Smuzhiyun # bitbake-worker can be run (usually this is the case, but it doesn't have to be) 399*4882a593Smuzhiyun path = os.getenv('PATH').split(':') 400*4882a593Smuzhiyun bitbakebinpath = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'bin')) 401*4882a593Smuzhiyun for entry in path: 402*4882a593Smuzhiyun if entry.endswith(os.sep): 403*4882a593Smuzhiyun entry = entry[:-1] 404*4882a593Smuzhiyun if os.path.abspath(entry) == bitbakebinpath: 405*4882a593Smuzhiyun break 406*4882a593Smuzhiyun else: 407*4882a593Smuzhiyun path.insert(0, bitbakebinpath) 408*4882a593Smuzhiyun os.environ['PATH'] = ':'.join(path) 409*4882a593Smuzhiyun 410*4882a593Smuzhiyun if self.server_connection: 411*4882a593Smuzhiyun _server_connections.append(self.server_connection) 412*4882a593Smuzhiyun if config_only: 413*4882a593Smuzhiyun config_params.updateToServer(self.server_connection.connection, os.environ.copy()) 414*4882a593Smuzhiyun self.run_command('parseConfiguration') 415*4882a593Smuzhiyun else: 416*4882a593Smuzhiyun self.run_actions(config_params) 417*4882a593Smuzhiyun self.recipes_parsed = True 418*4882a593Smuzhiyun 419*4882a593Smuzhiyun self.config_data = TinfoilDataStoreConnector(self, 0) 420*4882a593Smuzhiyun self.cooker = TinfoilCookerAdapter(self) 421*4882a593Smuzhiyun self.cooker_data = self.cooker.recipecaches[''] 422*4882a593Smuzhiyun else: 423*4882a593Smuzhiyun raise Exception('Failed to start bitbake server') 424*4882a593Smuzhiyun 425*4882a593Smuzhiyun def run_actions(self, config_params): 426*4882a593Smuzhiyun """ 427*4882a593Smuzhiyun Run the actions specified in config_params through the UI. 428*4882a593Smuzhiyun """ 429*4882a593Smuzhiyun ret = self.ui_module.main(self.server_connection.connection, self.server_connection.events, config_params) 430*4882a593Smuzhiyun if ret: 431*4882a593Smuzhiyun raise TinfoilUIException(ret) 432*4882a593Smuzhiyun 433*4882a593Smuzhiyun def parseRecipes(self): 434*4882a593Smuzhiyun """ 435*4882a593Smuzhiyun Legacy function - use parse_recipes() instead. 436*4882a593Smuzhiyun """ 437*4882a593Smuzhiyun self.parse_recipes() 438*4882a593Smuzhiyun 439*4882a593Smuzhiyun def parse_recipes(self): 440*4882a593Smuzhiyun """ 441*4882a593Smuzhiyun Load information on all recipes. Normally you should specify 442*4882a593Smuzhiyun config_only=False when calling prepare() instead of using this 443*4882a593Smuzhiyun function; this function is designed for situations where you need 444*4882a593Smuzhiyun to initialise Tinfoil and use it with config_only=True first and 445*4882a593Smuzhiyun then conditionally call this function to parse recipes later. 446*4882a593Smuzhiyun """ 447*4882a593Smuzhiyun config_params = TinfoilConfigParameters(config_only=False, quiet=self.quiet) 448*4882a593Smuzhiyun self.run_actions(config_params) 449*4882a593Smuzhiyun self.recipes_parsed = True 450*4882a593Smuzhiyun 451*4882a593Smuzhiyun def run_command(self, command, *params, handle_events=True): 452*4882a593Smuzhiyun """ 453*4882a593Smuzhiyun Run a command on the server (as implemented in bb.command). 454*4882a593Smuzhiyun Note that there are two types of command - synchronous and 455*4882a593Smuzhiyun asynchronous; in order to receive the results of asynchronous 456*4882a593Smuzhiyun commands you will need to set an appropriate event mask 457*4882a593Smuzhiyun using set_event_mask() and listen for the result using 458*4882a593Smuzhiyun wait_event() - with the correct event mask you'll at least get 459*4882a593Smuzhiyun bb.command.CommandCompleted and possibly other events before 460*4882a593Smuzhiyun that depending on the command. 461*4882a593Smuzhiyun """ 462*4882a593Smuzhiyun if not self.server_connection: 463*4882a593Smuzhiyun raise Exception('Not connected to server (did you call .prepare()?)') 464*4882a593Smuzhiyun 465*4882a593Smuzhiyun commandline = [command] 466*4882a593Smuzhiyun if params: 467*4882a593Smuzhiyun commandline.extend(params) 468*4882a593Smuzhiyun try: 469*4882a593Smuzhiyun result = self.server_connection.connection.runCommand(commandline) 470*4882a593Smuzhiyun finally: 471*4882a593Smuzhiyun while handle_events: 472*4882a593Smuzhiyun event = self.wait_event() 473*4882a593Smuzhiyun if not event: 474*4882a593Smuzhiyun break 475*4882a593Smuzhiyun if isinstance(event, logging.LogRecord): 476*4882a593Smuzhiyun if event.taskpid == 0 or event.levelno > logging.INFO: 477*4882a593Smuzhiyun self.logger.handle(event) 478*4882a593Smuzhiyun if result[1]: 479*4882a593Smuzhiyun raise TinfoilCommandFailed(result[1]) 480*4882a593Smuzhiyun return result[0] 481*4882a593Smuzhiyun 482*4882a593Smuzhiyun def set_event_mask(self, eventlist): 483*4882a593Smuzhiyun """Set the event mask which will be applied within wait_event()""" 484*4882a593Smuzhiyun if not self.server_connection: 485*4882a593Smuzhiyun raise Exception('Not connected to server (did you call .prepare()?)') 486*4882a593Smuzhiyun llevel, debug_domains = bb.msg.constructLogOptions() 487*4882a593Smuzhiyun ret = self.run_command('setEventMask', self.server_connection.connection.getEventHandle(), llevel, debug_domains, eventlist) 488*4882a593Smuzhiyun if not ret: 489*4882a593Smuzhiyun raise Exception('setEventMask failed') 490*4882a593Smuzhiyun 491*4882a593Smuzhiyun def wait_event(self, timeout=0): 492*4882a593Smuzhiyun """ 493*4882a593Smuzhiyun Wait for an event from the server for the specified time. 494*4882a593Smuzhiyun A timeout of 0 means don't wait if there are no events in the queue. 495*4882a593Smuzhiyun Returns the next event in the queue or None if the timeout was 496*4882a593Smuzhiyun reached. Note that in order to receive any events you will 497*4882a593Smuzhiyun first need to set the internal event mask using set_event_mask() 498*4882a593Smuzhiyun (otherwise whatever event mask the UI set up will be in effect). 499*4882a593Smuzhiyun """ 500*4882a593Smuzhiyun if not self.server_connection: 501*4882a593Smuzhiyun raise Exception('Not connected to server (did you call .prepare()?)') 502*4882a593Smuzhiyun return self.server_connection.events.waitEvent(timeout) 503*4882a593Smuzhiyun 504*4882a593Smuzhiyun def get_overlayed_recipes(self, mc=''): 505*4882a593Smuzhiyun """ 506*4882a593Smuzhiyun Find recipes which are overlayed (i.e. where recipes exist in multiple layers) 507*4882a593Smuzhiyun """ 508*4882a593Smuzhiyun return defaultdict(list, self.run_command('getOverlayedRecipes', mc)) 509*4882a593Smuzhiyun 510*4882a593Smuzhiyun def get_skipped_recipes(self): 511*4882a593Smuzhiyun """ 512*4882a593Smuzhiyun Find recipes which were skipped (i.e. SkipRecipe was raised 513*4882a593Smuzhiyun during parsing). 514*4882a593Smuzhiyun """ 515*4882a593Smuzhiyun return OrderedDict(self.run_command('getSkippedRecipes')) 516*4882a593Smuzhiyun 517*4882a593Smuzhiyun def get_all_providers(self, mc=''): 518*4882a593Smuzhiyun return defaultdict(list, self.run_command('allProviders', mc)) 519*4882a593Smuzhiyun 520*4882a593Smuzhiyun def find_providers(self, mc=''): 521*4882a593Smuzhiyun return self.run_command('findProviders', mc) 522*4882a593Smuzhiyun 523*4882a593Smuzhiyun def find_best_provider(self, pn): 524*4882a593Smuzhiyun return self.run_command('findBestProvider', pn) 525*4882a593Smuzhiyun 526*4882a593Smuzhiyun def get_runtime_providers(self, rdep): 527*4882a593Smuzhiyun return self.run_command('getRuntimeProviders', rdep) 528*4882a593Smuzhiyun 529*4882a593Smuzhiyun def get_recipe_file(self, pn): 530*4882a593Smuzhiyun """ 531*4882a593Smuzhiyun Get the file name for the specified recipe/target. Raises 532*4882a593Smuzhiyun bb.providers.NoProvider if there is no match or the recipe was 533*4882a593Smuzhiyun skipped. 534*4882a593Smuzhiyun """ 535*4882a593Smuzhiyun best = self.find_best_provider(pn) 536*4882a593Smuzhiyun if not best or (len(best) > 3 and not best[3]): 537*4882a593Smuzhiyun skiplist = self.get_skipped_recipes() 538*4882a593Smuzhiyun taskdata = bb.taskdata.TaskData(None, skiplist=skiplist) 539*4882a593Smuzhiyun skipreasons = taskdata.get_reasons(pn) 540*4882a593Smuzhiyun if skipreasons: 541*4882a593Smuzhiyun raise bb.providers.NoProvider('%s is unavailable:\n %s' % (pn, ' \n'.join(skipreasons))) 542*4882a593Smuzhiyun else: 543*4882a593Smuzhiyun raise bb.providers.NoProvider('Unable to find any recipe file matching "%s"' % pn) 544*4882a593Smuzhiyun return best[3] 545*4882a593Smuzhiyun 546*4882a593Smuzhiyun def get_file_appends(self, fn, mc=''): 547*4882a593Smuzhiyun """ 548*4882a593Smuzhiyun Find the bbappends for a recipe file 549*4882a593Smuzhiyun """ 550*4882a593Smuzhiyun return self.run_command('getFileAppends', fn, mc) 551*4882a593Smuzhiyun 552*4882a593Smuzhiyun def all_recipes(self, mc='', sort=True): 553*4882a593Smuzhiyun """ 554*4882a593Smuzhiyun Enable iterating over all recipes in the current configuration. 555*4882a593Smuzhiyun Returns an iterator over TinfoilRecipeInfo objects created on demand. 556*4882a593Smuzhiyun Parameters: 557*4882a593Smuzhiyun mc: The multiconfig, default of '' uses the main configuration. 558*4882a593Smuzhiyun sort: True to sort recipes alphabetically (default), False otherwise 559*4882a593Smuzhiyun """ 560*4882a593Smuzhiyun recipecache = self.cooker.recipecaches[mc] 561*4882a593Smuzhiyun if sort: 562*4882a593Smuzhiyun recipes = sorted(recipecache.pkg_pn.items()) 563*4882a593Smuzhiyun else: 564*4882a593Smuzhiyun recipes = recipecache.pkg_pn.items() 565*4882a593Smuzhiyun for pn, fns in recipes: 566*4882a593Smuzhiyun prov = self.find_best_provider(pn) 567*4882a593Smuzhiyun recipe = TinfoilRecipeInfo(recipecache, 568*4882a593Smuzhiyun self.config_data, 569*4882a593Smuzhiyun pn=pn, 570*4882a593Smuzhiyun fn=prov[3], 571*4882a593Smuzhiyun fns=fns) 572*4882a593Smuzhiyun yield recipe 573*4882a593Smuzhiyun 574*4882a593Smuzhiyun def all_recipe_files(self, mc='', variants=True, preferred_only=False): 575*4882a593Smuzhiyun """ 576*4882a593Smuzhiyun Enable iterating over all recipe files in the current configuration. 577*4882a593Smuzhiyun Returns an iterator over file paths. 578*4882a593Smuzhiyun Parameters: 579*4882a593Smuzhiyun mc: The multiconfig, default of '' uses the main configuration. 580*4882a593Smuzhiyun variants: True to include variants of recipes created through 581*4882a593Smuzhiyun BBCLASSEXTEND (default) or False to exclude them 582*4882a593Smuzhiyun preferred_only: True to include only the preferred recipe where 583*4882a593Smuzhiyun multiple exist providing the same PN, False to list 584*4882a593Smuzhiyun all recipes 585*4882a593Smuzhiyun """ 586*4882a593Smuzhiyun recipecache = self.cooker.recipecaches[mc] 587*4882a593Smuzhiyun if preferred_only: 588*4882a593Smuzhiyun files = [] 589*4882a593Smuzhiyun for pn in recipecache.pkg_pn.keys(): 590*4882a593Smuzhiyun prov = self.find_best_provider(pn) 591*4882a593Smuzhiyun files.append(prov[3]) 592*4882a593Smuzhiyun else: 593*4882a593Smuzhiyun files = recipecache.pkg_fn.keys() 594*4882a593Smuzhiyun for fn in sorted(files): 595*4882a593Smuzhiyun if not variants and fn.startswith('virtual:'): 596*4882a593Smuzhiyun continue 597*4882a593Smuzhiyun yield fn 598*4882a593Smuzhiyun 599*4882a593Smuzhiyun 600*4882a593Smuzhiyun def get_recipe_info(self, pn, mc=''): 601*4882a593Smuzhiyun """ 602*4882a593Smuzhiyun Get information on a specific recipe in the current configuration by name (PN). 603*4882a593Smuzhiyun Returns a TinfoilRecipeInfo object created on demand. 604*4882a593Smuzhiyun Parameters: 605*4882a593Smuzhiyun mc: The multiconfig, default of '' uses the main configuration. 606*4882a593Smuzhiyun """ 607*4882a593Smuzhiyun recipecache = self.cooker.recipecaches[mc] 608*4882a593Smuzhiyun prov = self.find_best_provider(pn) 609*4882a593Smuzhiyun fn = prov[3] 610*4882a593Smuzhiyun if fn: 611*4882a593Smuzhiyun actual_pn = recipecache.pkg_fn[fn] 612*4882a593Smuzhiyun recipe = TinfoilRecipeInfo(recipecache, 613*4882a593Smuzhiyun self.config_data, 614*4882a593Smuzhiyun pn=actual_pn, 615*4882a593Smuzhiyun fn=fn, 616*4882a593Smuzhiyun fns=recipecache.pkg_pn[actual_pn]) 617*4882a593Smuzhiyun return recipe 618*4882a593Smuzhiyun else: 619*4882a593Smuzhiyun return None 620*4882a593Smuzhiyun 621*4882a593Smuzhiyun def parse_recipe(self, pn): 622*4882a593Smuzhiyun """ 623*4882a593Smuzhiyun Parse the specified recipe and return a datastore object 624*4882a593Smuzhiyun representing the environment for the recipe. 625*4882a593Smuzhiyun """ 626*4882a593Smuzhiyun fn = self.get_recipe_file(pn) 627*4882a593Smuzhiyun return self.parse_recipe_file(fn) 628*4882a593Smuzhiyun 629*4882a593Smuzhiyun def parse_recipe_file(self, fn, appends=True, appendlist=None, config_data=None): 630*4882a593Smuzhiyun """ 631*4882a593Smuzhiyun Parse the specified recipe file (with or without bbappends) 632*4882a593Smuzhiyun and return a datastore object representing the environment 633*4882a593Smuzhiyun for the recipe. 634*4882a593Smuzhiyun Parameters: 635*4882a593Smuzhiyun fn: recipe file to parse - can be a file path or virtual 636*4882a593Smuzhiyun specification 637*4882a593Smuzhiyun appends: True to apply bbappends, False otherwise 638*4882a593Smuzhiyun appendlist: optional list of bbappend files to apply, if you 639*4882a593Smuzhiyun want to filter them 640*4882a593Smuzhiyun """ 641*4882a593Smuzhiyun if self.tracking: 642*4882a593Smuzhiyun # Enable history tracking just for the parse operation 643*4882a593Smuzhiyun self.run_command('enableDataTracking') 644*4882a593Smuzhiyun try: 645*4882a593Smuzhiyun if appends and appendlist == []: 646*4882a593Smuzhiyun appends = False 647*4882a593Smuzhiyun if config_data: 648*4882a593Smuzhiyun config_data = bb.data.createCopy(config_data) 649*4882a593Smuzhiyun dscon = self.run_command('parseRecipeFile', fn, appends, appendlist, config_data.dsindex) 650*4882a593Smuzhiyun else: 651*4882a593Smuzhiyun dscon = self.run_command('parseRecipeFile', fn, appends, appendlist) 652*4882a593Smuzhiyun if dscon: 653*4882a593Smuzhiyun return self._reconvert_type(dscon, 'DataStoreConnectionHandle') 654*4882a593Smuzhiyun else: 655*4882a593Smuzhiyun return None 656*4882a593Smuzhiyun finally: 657*4882a593Smuzhiyun if self.tracking: 658*4882a593Smuzhiyun self.run_command('disableDataTracking') 659*4882a593Smuzhiyun 660*4882a593Smuzhiyun def build_file(self, buildfile, task, internal=True): 661*4882a593Smuzhiyun """ 662*4882a593Smuzhiyun Runs the specified task for just a single recipe (i.e. no dependencies). 663*4882a593Smuzhiyun This is equivalent to bitbake -b, except with the default internal=True 664*4882a593Smuzhiyun no warning about dependencies will be produced, normal info messages 665*4882a593Smuzhiyun from the runqueue will be silenced and BuildInit, BuildStarted and 666*4882a593Smuzhiyun BuildCompleted events will not be fired. 667*4882a593Smuzhiyun """ 668*4882a593Smuzhiyun return self.run_command('buildFile', buildfile, task, internal) 669*4882a593Smuzhiyun 670*4882a593Smuzhiyun def build_targets(self, targets, task=None, handle_events=True, extra_events=None, event_callback=None): 671*4882a593Smuzhiyun """ 672*4882a593Smuzhiyun Builds the specified targets. This is equivalent to a normal invocation 673*4882a593Smuzhiyun of bitbake. Has built-in event handling which is enabled by default and 674*4882a593Smuzhiyun can be extended if needed. 675*4882a593Smuzhiyun Parameters: 676*4882a593Smuzhiyun targets: 677*4882a593Smuzhiyun One or more targets to build. Can be a list or a 678*4882a593Smuzhiyun space-separated string. 679*4882a593Smuzhiyun task: 680*4882a593Smuzhiyun The task to run; if None then the value of BB_DEFAULT_TASK 681*4882a593Smuzhiyun will be used. Default None. 682*4882a593Smuzhiyun handle_events: 683*4882a593Smuzhiyun True to handle events in a similar way to normal bitbake 684*4882a593Smuzhiyun invocation with knotty; False to return immediately (on the 685*4882a593Smuzhiyun assumption that the caller will handle the events instead). 686*4882a593Smuzhiyun Default True. 687*4882a593Smuzhiyun extra_events: 688*4882a593Smuzhiyun An optional list of events to add to the event mask (if 689*4882a593Smuzhiyun handle_events=True). If you add events here you also need 690*4882a593Smuzhiyun to specify a callback function in event_callback that will 691*4882a593Smuzhiyun handle the additional events. Default None. 692*4882a593Smuzhiyun event_callback: 693*4882a593Smuzhiyun An optional function taking a single parameter which 694*4882a593Smuzhiyun will be called first upon receiving any event (if 695*4882a593Smuzhiyun handle_events=True) so that the caller can override or 696*4882a593Smuzhiyun extend the event handling. Default None. 697*4882a593Smuzhiyun """ 698*4882a593Smuzhiyun if isinstance(targets, str): 699*4882a593Smuzhiyun targets = targets.split() 700*4882a593Smuzhiyun if not task: 701*4882a593Smuzhiyun task = self.config_data.getVar('BB_DEFAULT_TASK') 702*4882a593Smuzhiyun 703*4882a593Smuzhiyun if handle_events: 704*4882a593Smuzhiyun # A reasonable set of default events matching up with those we handle below 705*4882a593Smuzhiyun eventmask = [ 706*4882a593Smuzhiyun 'bb.event.BuildStarted', 707*4882a593Smuzhiyun 'bb.event.BuildCompleted', 708*4882a593Smuzhiyun 'logging.LogRecord', 709*4882a593Smuzhiyun 'bb.event.NoProvider', 710*4882a593Smuzhiyun 'bb.command.CommandCompleted', 711*4882a593Smuzhiyun 'bb.command.CommandFailed', 712*4882a593Smuzhiyun 'bb.build.TaskStarted', 713*4882a593Smuzhiyun 'bb.build.TaskFailed', 714*4882a593Smuzhiyun 'bb.build.TaskSucceeded', 715*4882a593Smuzhiyun 'bb.build.TaskFailedSilent', 716*4882a593Smuzhiyun 'bb.build.TaskProgress', 717*4882a593Smuzhiyun 'bb.runqueue.runQueueTaskStarted', 718*4882a593Smuzhiyun 'bb.runqueue.sceneQueueTaskStarted', 719*4882a593Smuzhiyun 'bb.event.ProcessStarted', 720*4882a593Smuzhiyun 'bb.event.ProcessProgress', 721*4882a593Smuzhiyun 'bb.event.ProcessFinished', 722*4882a593Smuzhiyun ] 723*4882a593Smuzhiyun if extra_events: 724*4882a593Smuzhiyun eventmask.extend(extra_events) 725*4882a593Smuzhiyun ret = self.set_event_mask(eventmask) 726*4882a593Smuzhiyun 727*4882a593Smuzhiyun includelogs = self.config_data.getVar('BBINCLUDELOGS') 728*4882a593Smuzhiyun loglines = self.config_data.getVar('BBINCLUDELOGS_LINES') 729*4882a593Smuzhiyun 730*4882a593Smuzhiyun ret = self.run_command('buildTargets', targets, task) 731*4882a593Smuzhiyun if handle_events: 732*4882a593Smuzhiyun result = False 733*4882a593Smuzhiyun # Borrowed from knotty, instead somewhat hackily we use the helper 734*4882a593Smuzhiyun # as the object to store "shutdown" on 735*4882a593Smuzhiyun helper = bb.ui.uihelper.BBUIHelper() 736*4882a593Smuzhiyun helper.shutdown = 0 737*4882a593Smuzhiyun parseprogress = None 738*4882a593Smuzhiyun termfilter = bb.ui.knotty.TerminalFilter(helper, helper, self.logger.handlers, quiet=self.quiet) 739*4882a593Smuzhiyun try: 740*4882a593Smuzhiyun while True: 741*4882a593Smuzhiyun try: 742*4882a593Smuzhiyun event = self.wait_event(0.25) 743*4882a593Smuzhiyun if event: 744*4882a593Smuzhiyun if event_callback and event_callback(event): 745*4882a593Smuzhiyun continue 746*4882a593Smuzhiyun if helper.eventHandler(event): 747*4882a593Smuzhiyun if isinstance(event, bb.build.TaskFailedSilent): 748*4882a593Smuzhiyun self.logger.warning("Logfile for failed setscene task is %s" % event.logfile) 749*4882a593Smuzhiyun elif isinstance(event, bb.build.TaskFailed): 750*4882a593Smuzhiyun bb.ui.knotty.print_event_log(event, includelogs, loglines, termfilter) 751*4882a593Smuzhiyun continue 752*4882a593Smuzhiyun if isinstance(event, bb.event.ProcessStarted): 753*4882a593Smuzhiyun if self.quiet > 1: 754*4882a593Smuzhiyun continue 755*4882a593Smuzhiyun parseprogress = bb.ui.knotty.new_progress(event.processname, event.total) 756*4882a593Smuzhiyun parseprogress.start(False) 757*4882a593Smuzhiyun continue 758*4882a593Smuzhiyun if isinstance(event, bb.event.ProcessProgress): 759*4882a593Smuzhiyun if self.quiet > 1: 760*4882a593Smuzhiyun continue 761*4882a593Smuzhiyun if parseprogress: 762*4882a593Smuzhiyun parseprogress.update(event.progress) 763*4882a593Smuzhiyun else: 764*4882a593Smuzhiyun bb.warn("Got ProcessProgress event for something that never started?") 765*4882a593Smuzhiyun continue 766*4882a593Smuzhiyun if isinstance(event, bb.event.ProcessFinished): 767*4882a593Smuzhiyun if self.quiet > 1: 768*4882a593Smuzhiyun continue 769*4882a593Smuzhiyun if parseprogress: 770*4882a593Smuzhiyun parseprogress.finish() 771*4882a593Smuzhiyun parseprogress = None 772*4882a593Smuzhiyun continue 773*4882a593Smuzhiyun if isinstance(event, bb.command.CommandCompleted): 774*4882a593Smuzhiyun result = True 775*4882a593Smuzhiyun break 776*4882a593Smuzhiyun if isinstance(event, bb.command.CommandFailed): 777*4882a593Smuzhiyun self.logger.error(str(event)) 778*4882a593Smuzhiyun result = False 779*4882a593Smuzhiyun break 780*4882a593Smuzhiyun if isinstance(event, logging.LogRecord): 781*4882a593Smuzhiyun if event.taskpid == 0 or event.levelno > logging.INFO: 782*4882a593Smuzhiyun self.logger.handle(event) 783*4882a593Smuzhiyun continue 784*4882a593Smuzhiyun if isinstance(event, bb.event.NoProvider): 785*4882a593Smuzhiyun self.logger.error(str(event)) 786*4882a593Smuzhiyun result = False 787*4882a593Smuzhiyun break 788*4882a593Smuzhiyun 789*4882a593Smuzhiyun elif helper.shutdown > 1: 790*4882a593Smuzhiyun break 791*4882a593Smuzhiyun termfilter.updateFooter() 792*4882a593Smuzhiyun except KeyboardInterrupt: 793*4882a593Smuzhiyun termfilter.clearFooter() 794*4882a593Smuzhiyun if helper.shutdown == 1: 795*4882a593Smuzhiyun print("\nSecond Keyboard Interrupt, stopping...\n") 796*4882a593Smuzhiyun ret = self.run_command("stateForceShutdown") 797*4882a593Smuzhiyun if ret and ret[2]: 798*4882a593Smuzhiyun self.logger.error("Unable to cleanly stop: %s" % ret[2]) 799*4882a593Smuzhiyun elif helper.shutdown == 0: 800*4882a593Smuzhiyun print("\nKeyboard Interrupt, closing down...\n") 801*4882a593Smuzhiyun interrupted = True 802*4882a593Smuzhiyun ret = self.run_command("stateShutdown") 803*4882a593Smuzhiyun if ret and ret[2]: 804*4882a593Smuzhiyun self.logger.error("Unable to cleanly shutdown: %s" % ret[2]) 805*4882a593Smuzhiyun helper.shutdown = helper.shutdown + 1 806*4882a593Smuzhiyun termfilter.clearFooter() 807*4882a593Smuzhiyun finally: 808*4882a593Smuzhiyun termfilter.finish() 809*4882a593Smuzhiyun if helper.failed_tasks: 810*4882a593Smuzhiyun result = False 811*4882a593Smuzhiyun return result 812*4882a593Smuzhiyun else: 813*4882a593Smuzhiyun return ret 814*4882a593Smuzhiyun 815*4882a593Smuzhiyun def shutdown(self): 816*4882a593Smuzhiyun """ 817*4882a593Smuzhiyun Shut down tinfoil. Disconnects from the server and gracefully 818*4882a593Smuzhiyun releases any associated resources. You must call this function if 819*4882a593Smuzhiyun prepare() has been called, or use a with... block when you create 820*4882a593Smuzhiyun the tinfoil object which will ensure that it gets called. 821*4882a593Smuzhiyun """ 822*4882a593Smuzhiyun try: 823*4882a593Smuzhiyun if self.server_connection: 824*4882a593Smuzhiyun try: 825*4882a593Smuzhiyun self.run_command('clientComplete') 826*4882a593Smuzhiyun finally: 827*4882a593Smuzhiyun _server_connections.remove(self.server_connection) 828*4882a593Smuzhiyun bb.event.ui_queue = [] 829*4882a593Smuzhiyun self.server_connection.terminate() 830*4882a593Smuzhiyun self.server_connection = None 831*4882a593Smuzhiyun 832*4882a593Smuzhiyun finally: 833*4882a593Smuzhiyun # Restore logging handlers to how it looked when we started 834*4882a593Smuzhiyun if self.oldhandlers: 835*4882a593Smuzhiyun for handler in self.logger.handlers: 836*4882a593Smuzhiyun if handler not in self.oldhandlers: 837*4882a593Smuzhiyun self.logger.handlers.remove(handler) 838*4882a593Smuzhiyun 839*4882a593Smuzhiyun def _reconvert_type(self, obj, origtypename): 840*4882a593Smuzhiyun """ 841*4882a593Smuzhiyun Convert an object back to the right type, in the case 842*4882a593Smuzhiyun that marshalling has changed it (especially with xmlrpc) 843*4882a593Smuzhiyun """ 844*4882a593Smuzhiyun supported_types = { 845*4882a593Smuzhiyun 'set': set, 846*4882a593Smuzhiyun 'DataStoreConnectionHandle': bb.command.DataStoreConnectionHandle, 847*4882a593Smuzhiyun } 848*4882a593Smuzhiyun 849*4882a593Smuzhiyun origtype = supported_types.get(origtypename, None) 850*4882a593Smuzhiyun if origtype is None: 851*4882a593Smuzhiyun raise Exception('Unsupported type "%s"' % origtypename) 852*4882a593Smuzhiyun if type(obj) == origtype: 853*4882a593Smuzhiyun newobj = obj 854*4882a593Smuzhiyun elif isinstance(obj, dict): 855*4882a593Smuzhiyun # New style class 856*4882a593Smuzhiyun newobj = origtype() 857*4882a593Smuzhiyun for k,v in obj.items(): 858*4882a593Smuzhiyun setattr(newobj, k, v) 859*4882a593Smuzhiyun else: 860*4882a593Smuzhiyun # Assume we can coerce the type 861*4882a593Smuzhiyun newobj = origtype(obj) 862*4882a593Smuzhiyun 863*4882a593Smuzhiyun if isinstance(newobj, bb.command.DataStoreConnectionHandle): 864*4882a593Smuzhiyun newobj = TinfoilDataStoreConnector(self, newobj.dsindex) 865*4882a593Smuzhiyun 866*4882a593Smuzhiyun return newobj 867*4882a593Smuzhiyun 868*4882a593Smuzhiyun 869*4882a593Smuzhiyunclass TinfoilConfigParameters(BitBakeConfigParameters): 870*4882a593Smuzhiyun 871*4882a593Smuzhiyun def __init__(self, config_only, **options): 872*4882a593Smuzhiyun self.initial_options = options 873*4882a593Smuzhiyun # Apply some sane defaults 874*4882a593Smuzhiyun if not 'parse_only' in options: 875*4882a593Smuzhiyun self.initial_options['parse_only'] = not config_only 876*4882a593Smuzhiyun #if not 'status_only' in options: 877*4882a593Smuzhiyun # self.initial_options['status_only'] = config_only 878*4882a593Smuzhiyun if not 'ui' in options: 879*4882a593Smuzhiyun self.initial_options['ui'] = 'knotty' 880*4882a593Smuzhiyun if not 'argv' in options: 881*4882a593Smuzhiyun self.initial_options['argv'] = [] 882*4882a593Smuzhiyun 883*4882a593Smuzhiyun super(TinfoilConfigParameters, self).__init__() 884*4882a593Smuzhiyun 885*4882a593Smuzhiyun def parseCommandLine(self, argv=None): 886*4882a593Smuzhiyun # We don't want any parameters parsed from the command line 887*4882a593Smuzhiyun opts = super(TinfoilConfigParameters, self).parseCommandLine([]) 888*4882a593Smuzhiyun for key, val in self.initial_options.items(): 889*4882a593Smuzhiyun setattr(opts[0], key, val) 890*4882a593Smuzhiyun return opts 891