__init__.py 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364
  1. # Copyright (C) 2016-2018 Wind River Systems, Inc.
  2. #
  3. # This program is free software; you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License version 2 as
  5. # published by the Free Software Foundation.
  6. #
  7. # This program is distributed in the hope that it will be useful,
  8. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. # See the GNU General Public License for more details.
  11. #
  12. # You should have received a copy of the GNU General Public License
  13. # along with this program; if not, write to the Free Software
  14. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  15. import datetime
  16. import logging
  17. import imp
  18. from collections import OrderedDict
  19. from layerindexlib.plugin import LayerIndexPluginUrlError
  20. logger = logging.getLogger('BitBake.layerindexlib')
  21. # Exceptions
  22. class LayerIndexException(Exception):
  23. '''LayerIndex Generic Exception'''
  24. def __init__(self, message):
  25. self.msg = message
  26. Exception.__init__(self, message)
  27. def __str__(self):
  28. return self.msg
  29. class LayerIndexUrlError(LayerIndexException):
  30. '''Exception raised when unable to access a URL for some reason'''
  31. def __init__(self, url, message=""):
  32. if message:
  33. msg = "Unable to access layerindex url %s: %s" % (url, message)
  34. else:
  35. msg = "Unable to access layerindex url %s" % url
  36. self.url = url
  37. LayerIndexException.__init__(self, msg)
  38. class LayerIndexFetchError(LayerIndexException):
  39. '''General layerindex fetcher exception when something fails'''
  40. def __init__(self, url, message=""):
  41. if message:
  42. msg = "Unable to fetch layerindex url %s: %s" % (url, message)
  43. else:
  44. msg = "Unable to fetch layerindex url %s" % url
  45. self.url = url
  46. LayerIndexException.__init__(self, msg)
  47. # Interface to the overall layerindex system
  48. # the layer may contain one or more individual indexes
  49. class LayerIndex():
  50. def __init__(self, d):
  51. if not d:
  52. raise LayerIndexException("Must be initialized with bb.data.")
  53. self.data = d
  54. # List of LayerIndexObj
  55. self.indexes = []
  56. self.plugins = []
  57. import bb.utils
  58. bb.utils.load_plugins(logger, self.plugins, os.path.dirname(__file__))
  59. for plugin in self.plugins:
  60. if hasattr(plugin, 'init'):
  61. plugin.init(self)
  62. def __add__(self, other):
  63. newIndex = LayerIndex(self.data)
  64. if self.__class__ != newIndex.__class__ or \
  65. other.__class__ != newIndex.__class__:
  66. raise TypeException("Can not add different types.")
  67. for indexEnt in self.indexes:
  68. newIndex.indexes.append(indexEnt)
  69. for indexEnt in other.indexes:
  70. newIndex.indexes.append(indexEnt)
  71. return newIndex
  72. def _parse_params(self, params):
  73. '''Take a parameter list, return a dictionary of parameters.
  74. Expected to be called from the data of urllib.parse.urlparse(url).params
  75. If there are two conflicting parameters, last in wins...
  76. '''
  77. param_dict = {}
  78. for param in params.split(';'):
  79. if not param:
  80. continue
  81. item = param.split('=', 1)
  82. logger.debug(1, item)
  83. param_dict[item[0]] = item[1]
  84. return param_dict
  85. def _fetch_url(self, url, username=None, password=None, debuglevel=0):
  86. '''Fetch data from a specific URL.
  87. Fetch something from a specific URL. This is specifically designed to
  88. fetch data from a layerindex-web instance, but may be useful for other
  89. raw fetch actions.
  90. It is not designed to be used to fetch recipe sources or similar. the
  91. regular fetcher class should used for that.
  92. It is the responsibility of the caller to check BB_NO_NETWORK and related
  93. BB_ALLOWED_NETWORKS.
  94. '''
  95. if not url:
  96. raise LayerIndexUrlError(url, "empty url")
  97. import urllib
  98. from urllib.request import urlopen, Request
  99. from urllib.parse import urlparse
  100. up = urlparse(url)
  101. if username:
  102. logger.debug(1, "Configuring authentication for %s..." % url)
  103. password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
  104. password_mgr.add_password(None, "%s://%s" % (up.scheme, up.netloc), username, password)
  105. handler = urllib.request.HTTPBasicAuthHandler(password_mgr)
  106. opener = urllib.request.build_opener(handler, urllib.request.HTTPSHandler(debuglevel=debuglevel))
  107. else:
  108. opener = urllib.request.build_opener(urllib.request.HTTPSHandler(debuglevel=debuglevel))
  109. urllib.request.install_opener(opener)
  110. logger.debug(1, "Fetching %s (%s)..." % (url, ["without authentication", "with authentication"][bool(username)]))
  111. try:
  112. res = urlopen(Request(url, headers={'User-Agent': 'Mozilla/5.0 (bitbake/lib/layerindex)'}, unverifiable=True))
  113. except urllib.error.HTTPError as e:
  114. logger.debug(1, "HTTP Error: %s: %s" % (e.code, e.reason))
  115. logger.debug(1, " Requested: %s" % (url))
  116. logger.debug(1, " Actual: %s" % (e.geturl()))
  117. if e.code == 404:
  118. logger.debug(1, "Request not found.")
  119. raise LayerIndexFetchError(url, e)
  120. else:
  121. logger.debug(1, "Headers:\n%s" % (e.headers))
  122. raise LayerIndexFetchError(url, e)
  123. except OSError as e:
  124. error = 0
  125. reason = ""
  126. # Process base OSError first...
  127. if hasattr(e, 'errno'):
  128. error = e.errno
  129. reason = e.strerror
  130. # Process gaierror (socket error) subclass if available.
  131. if hasattr(e, 'reason') and hasattr(e.reason, 'errno') and hasattr(e.reason, 'strerror'):
  132. error = e.reason.errno
  133. reason = e.reason.strerror
  134. if error == -2:
  135. raise LayerIndexFetchError(url, "%s: %s" % (e, reason))
  136. if error and error != 0:
  137. raise LayerIndexFetchError(url, "Unexpected exception: [Error %s] %s" % (error, reason))
  138. else:
  139. raise LayerIndexFetchError(url, "Unable to fetch OSError exception: %s" % e)
  140. finally:
  141. logger.debug(1, "...fetching %s (%s), done." % (url, ["without authentication", "with authentication"][bool(username)]))
  142. return res
  143. def load_layerindex(self, indexURI, load=['layerDependencies', 'recipes', 'machines', 'distros'], reload=False):
  144. '''Load the layerindex.
  145. indexURI - An index to load. (Use multiple calls to load multiple indexes)
  146. reload - If reload is True, then any previously loaded indexes will be forgotten.
  147. load - List of elements to load. Default loads all items.
  148. Note: plugs may ignore this.
  149. The format of the indexURI:
  150. <url>;branch=<branch>;cache=<cache>;desc=<description>
  151. Note: the 'branch' parameter if set can select multiple branches by using
  152. comma, such as 'branch=master,morty,pyro'. However, many operations only look
  153. at the -first- branch specified!
  154. The cache value may be undefined, in this case a network failure will
  155. result in an error, otherwise the system will look for a file of the cache
  156. name and load that instead.
  157. For example:
  158. http://layers.openembedded.org/layerindex/api/;branch=master;desc=OpenEmbedded%20Layer%20Index
  159. cooker://
  160. '''
  161. if reload:
  162. self.indexes = []
  163. logger.debug(1, 'Loading: %s' % indexURI)
  164. if not self.plugins:
  165. raise LayerIndexException("No LayerIndex Plugins available")
  166. for plugin in self.plugins:
  167. # Check if the plugin was initialized
  168. logger.debug(1, 'Trying %s' % plugin.__class__)
  169. if not hasattr(plugin, 'type') or not plugin.type:
  170. continue
  171. try:
  172. # TODO: Implement 'cache', for when the network is not available
  173. indexEnt = plugin.load_index(indexURI, load)
  174. break
  175. except LayerIndexPluginUrlError as e:
  176. logger.debug(1, "%s doesn't support %s" % (plugin.type, e.url))
  177. except NotImplementedError:
  178. pass
  179. else:
  180. logger.debug(1, "No plugins support %s" % indexURI)
  181. raise LayerIndexException("No plugins support %s" % indexURI)
  182. # Mark CONFIG data as something we've added...
  183. indexEnt.config['local'] = []
  184. indexEnt.config['local'].append('config')
  185. # No longer permit changes..
  186. indexEnt.lockData()
  187. self.indexes.append(indexEnt)
  188. def store_layerindex(self, indexURI, index=None):
  189. '''Store one layerindex
  190. Typically this will be used to create a local cache file of a remote index.
  191. file://<path>;branch=<branch>
  192. We can write out in either the restapi or django formats. The split option
  193. will write out the individual elements split by layer and related components.
  194. '''
  195. if not index:
  196. logger.warning('No index to write, nothing to do.')
  197. return
  198. if not self.plugins:
  199. raise LayerIndexException("No LayerIndex Plugins available")
  200. for plugin in self.plugins:
  201. # Check if the plugin was initialized
  202. logger.debug(1, 'Trying %s' % plugin.__class__)
  203. if not hasattr(plugin, 'type') or not plugin.type:
  204. continue
  205. try:
  206. plugin.store_index(indexURI, index)
  207. break
  208. except LayerIndexPluginUrlError as e:
  209. logger.debug(1, "%s doesn't support %s" % (plugin.type, e.url))
  210. except NotImplementedError:
  211. logger.debug(1, "Store not implemented in %s" % plugin.type)
  212. pass
  213. else:
  214. logger.debug(1, "No plugins support %s" % url)
  215. raise LayerIndexException("No plugins support %s" % url)
  216. def is_empty(self):
  217. '''Return True or False if the index has any usable data.
  218. We check the indexes entries to see if they have a branch set, as well as
  219. layerBranches set. If not, they are effectively blank.'''
  220. found = False
  221. for index in self.indexes:
  222. if index.__bool__():
  223. found = True
  224. break
  225. return not found
  226. def find_vcs_url(self, vcs_url, branch=None):
  227. '''Return the first layerBranch with the given vcs_url
  228. If a branch has not been specified, we will iterate over the branches in
  229. the default configuration until the first vcs_url/branch match.'''
  230. for index in self.indexes:
  231. logger.debug(1, ' searching %s' % index.config['DESCRIPTION'])
  232. layerBranch = index.find_vcs_url(vcs_url, [branch])
  233. if layerBranch:
  234. return layerBranch
  235. return None
  236. def find_collection(self, collection, version=None, branch=None):
  237. '''Return the first layerBranch with the given collection name
  238. If a branch has not been specified, we will iterate over the branches in
  239. the default configuration until the first collection/branch match.'''
  240. logger.debug(1, 'find_collection: %s (%s) %s' % (collection, version, branch))
  241. if branch:
  242. branches = [branch]
  243. else:
  244. branches = None
  245. for index in self.indexes:
  246. logger.debug(1, ' searching %s' % index.config['DESCRIPTION'])
  247. layerBranch = index.find_collection(collection, version, branches)
  248. if layerBranch:
  249. return layerBranch
  250. else:
  251. logger.debug(1, 'Collection %s (%s) not found for branch (%s)' % (collection, version, branch))
  252. return None
  253. def find_layerbranch(self, name, branch=None):
  254. '''Return the layerBranch item for a given name and branch
  255. If a branch has not been specified, we will iterate over the branches in
  256. the default configuration until the first name/branch match.'''
  257. if branch:
  258. branches = [branch]
  259. else:
  260. branches = None
  261. for index in self.indexes:
  262. layerBranch = index.find_layerbranch(name, branches)
  263. if layerBranch:
  264. return layerBranch
  265. return None
  266. def find_dependencies(self, names=None, layerbranches=None, ignores=None):
  267. '''Return a tuple of all dependencies and valid items for the list of (layer) names
  268. The dependency scanning happens depth-first. The returned
  269. dependencies should be in the best order to define bblayers.
  270. names - list of layer names (searching layerItems)
  271. branches - when specified (with names) only this list of branches are evaluated
  272. layerbranches - list of layerbranches to resolve dependencies
  273. ignores - list of layer names to ignore
  274. return: (dependencies, invalid)
  275. dependencies[LayerItem.name] = [ LayerBranch, LayerDependency1, LayerDependency2, ... ]
  276. invalid = [ LayerItem.name1, LayerItem.name2, ... ]
  277. '''
  278. invalid = []
  279. # Convert name/branch to layerbranches
  280. if layerbranches is None:
  281. layerbranches = []
  282. for name in names:
  283. if ignores and name in ignores:
  284. continue
  285. for index in self.indexes:
  286. layerbranch = index.find_layerbranch(name)
  287. if not layerbranch:
  288. # Not in this index, hopefully it's in another...
  289. continue
  290. layerbranches.append(layerbranch)
  291. break
  292. else:
  293. invalid.append(name)
  294. def _resolve_dependencies(layerbranches, ignores, dependencies, invalid):
  295. for layerbranch in layerbranches:
  296. if ignores and layerbranch.layer.name in ignores:
  297. continue
  298. # Get a list of dependencies and then recursively process them
  299. for layerdependency in layerbranch.index.layerDependencies_layerBranchId[layerbranch.id]:
  300. deplayerbranch = layerdependency.dependency_layerBranch
  301. if ignores and deplayerbranch.layer.name in ignores:
  302. continue
  303. # This little block is why we can't re-use the LayerIndexObj version,
  304. # we must be able to satisfy each dependencies across layer indexes and
  305. # use the layer index order for priority. (r stands for replacement below)
  306. # If this is the primary index, we can fast path and skip this
  307. if deplayerbranch.index != self.indexes[0]:
  308. # Is there an entry in a prior index for this collection/version?
  309. rdeplayerbranch = self.find_collection(
  310. collection=deplayerbranch.collection,
  311. version=deplayerbranch.version
  312. )
  313. if rdeplayerbranch != deplayerbranch:
  314. logger.debug(1, 'Replaced %s:%s:%s with %s:%s:%s' % \
  315. (deplayerbranch.index.config['DESCRIPTION'],
  316. deplayerbranch.branch.name,
  317. deplayerbranch.layer.name,
  318. rdeplayerbranch.index.config['DESCRIPTION'],
  319. rdeplayerbranch.branch.name,
  320. rdeplayerbranch.layer.name))
  321. deplayerbranch = rdeplayerbranch
  322. # New dependency, we need to resolve it now... depth-first
  323. if deplayerbranch.layer.name not in dependencies:
  324. (dependencies, invalid) = _resolve_dependencies([deplayerbranch], ignores, dependencies, invalid)
  325. if deplayerbranch.layer.name not in dependencies:
  326. dependencies[deplayerbranch.layer.name] = [deplayerbranch, layerdependency]
  327. else:
  328. if layerdependency not in dependencies[deplayerbranch.layer.name]:
  329. dependencies[deplayerbranch.layer.name].append(layerdependency)
  330. return (dependencies, invalid)
  331. # OK, resolve this one...
  332. dependencies = OrderedDict()
  333. (dependencies, invalid) = _resolve_dependencies(layerbranches, ignores, dependencies, invalid)
  334. for layerbranch in layerbranches:
  335. if layerbranch.layer.name not in dependencies:
  336. dependencies[layerbranch.layer.name] = [layerbranch]
  337. return (dependencies, invalid)
  338. def list_obj(self, object):
  339. '''Print via the plain logger object information
  340. This function is used to implement debugging and provide the user info.
  341. '''
  342. for lix in self.indexes:
  343. if object not in lix:
  344. continue
  345. logger.plain ('')
  346. logger.plain ('Index: %s' % lix.config['DESCRIPTION'])
  347. output = []
  348. if object == 'branches':
  349. logger.plain ('%s %s %s' % ('{:26}'.format('branch'), '{:34}'.format('description'), '{:22}'.format('bitbake branch')))
  350. logger.plain ('{:-^80}'.format(""))
  351. for branchid in lix.branches:
  352. output.append('%s %s %s' % (
  353. '{:26}'.format(lix.branches[branchid].name),
  354. '{:34}'.format(lix.branches[branchid].short_description),
  355. '{:22}'.format(lix.branches[branchid].bitbake_branch)
  356. ))
  357. for line in sorted(output):
  358. logger.plain (line)
  359. continue
  360. if object == 'layerItems':
  361. logger.plain ('%s %s' % ('{:26}'.format('layer'), '{:34}'.format('description')))
  362. logger.plain ('{:-^80}'.format(""))
  363. for layerid in lix.layerItems:
  364. output.append('%s %s' % (
  365. '{:26}'.format(lix.layerItems[layerid].name),
  366. '{:34}'.format(lix.layerItems[layerid].summary)
  367. ))
  368. for line in sorted(output):
  369. logger.plain (line)
  370. continue
  371. if object == 'layerBranches':
  372. logger.plain ('%s %s %s' % ('{:26}'.format('layer'), '{:34}'.format('description'), '{:19}'.format('collection:version')))
  373. logger.plain ('{:-^80}'.format(""))
  374. for layerbranchid in lix.layerBranches:
  375. output.append('%s %s %s' % (
  376. '{:26}'.format(lix.layerBranches[layerbranchid].layer.name),
  377. '{:34}'.format(lix.layerBranches[layerbranchid].layer.summary),
  378. '{:19}'.format("%s:%s" %
  379. (lix.layerBranches[layerbranchid].collection,
  380. lix.layerBranches[layerbranchid].version)
  381. )
  382. ))
  383. for line in sorted(output):
  384. logger.plain (line)
  385. continue
  386. if object == 'layerDependencies':
  387. logger.plain ('%s %s %s %s' % ('{:19}'.format('branch'), '{:26}'.format('layer'), '{:11}'.format('dependency'), '{:26}'.format('layer')))
  388. logger.plain ('{:-^80}'.format(""))
  389. for layerDependency in lix.layerDependencies:
  390. if not lix.layerDependencies[layerDependency].dependency_layerBranch:
  391. continue
  392. output.append('%s %s %s %s' % (
  393. '{:19}'.format(lix.layerDependencies[layerDependency].layerbranch.branch.name),
  394. '{:26}'.format(lix.layerDependencies[layerDependency].layerbranch.layer.name),
  395. '{:11}'.format('requires' if lix.layerDependencies[layerDependency].required else 'recommends'),
  396. '{:26}'.format(lix.layerDependencies[layerDependency].dependency_layerBranch.layer.name)
  397. ))
  398. for line in sorted(output):
  399. logger.plain (line)
  400. continue
  401. if object == 'recipes':
  402. logger.plain ('%s %s %s' % ('{:20}'.format('recipe'), '{:10}'.format('version'), 'layer'))
  403. logger.plain ('{:-^80}'.format(""))
  404. output = []
  405. for recipe in lix.recipes:
  406. output.append('%s %s %s' % (
  407. '{:30}'.format(lix.recipes[recipe].pn),
  408. '{:30}'.format(lix.recipes[recipe].pv),
  409. lix.recipes[recipe].layer.name
  410. ))
  411. for line in sorted(output):
  412. logger.plain (line)
  413. continue
  414. if object == 'machines':
  415. logger.plain ('%s %s %s' % ('{:24}'.format('machine'), '{:34}'.format('description'), '{:19}'.format('layer')))
  416. logger.plain ('{:-^80}'.format(""))
  417. for machine in lix.machines:
  418. output.append('%s %s %s' % (
  419. '{:24}'.format(lix.machines[machine].name),
  420. '{:34}'.format(lix.machines[machine].description)[:34],
  421. '{:19}'.format(lix.machines[machine].layerbranch.layer.name)
  422. ))
  423. for line in sorted(output):
  424. logger.plain (line)
  425. continue
  426. if object == 'distros':
  427. logger.plain ('%s %s %s' % ('{:24}'.format('distro'), '{:34}'.format('description'), '{:19}'.format('layer')))
  428. logger.plain ('{:-^80}'.format(""))
  429. for distro in lix.distros:
  430. output.append('%s %s %s' % (
  431. '{:24}'.format(lix.distros[distro].name),
  432. '{:34}'.format(lix.distros[distro].description)[:34],
  433. '{:19}'.format(lix.distros[distro].layerbranch.layer.name)
  434. ))
  435. for line in sorted(output):
  436. logger.plain (line)
  437. continue
  438. logger.plain ('')
  439. # This class holds a single layer index instance
  440. # The LayerIndexObj is made up of dictionary of elements, such as:
  441. # index['config'] - configuration data for this index
  442. # index['branches'] - dictionary of Branch objects, by id number
  443. # index['layerItems'] - dictionary of layerItem objects, by id number
  444. # ...etc... (See: http://layers.openembedded.org/layerindex/api/)
  445. #
  446. # The class needs to manage the 'index' entries and allow easily adding
  447. # of new items, as well as simply loading of the items.
  448. class LayerIndexObj():
  449. def __init__(self):
  450. super().__setattr__('_index', {})
  451. super().__setattr__('_lock', False)
  452. def __bool__(self):
  453. '''False if the index is effectively empty
  454. We check the index to see if it has a branch set, as well as
  455. layerbranches set. If not, it is effectively blank.'''
  456. if not bool(self._index):
  457. return False
  458. try:
  459. if self.branches and self.layerBranches:
  460. return True
  461. except AttributeError:
  462. pass
  463. return False
  464. def __getattr__(self, name):
  465. if name.startswith('_'):
  466. return super().__getattribute__(name)
  467. if name not in self._index:
  468. raise AttributeError('%s not in index datastore' % name)
  469. return self._index[name]
  470. def __setattr__(self, name, value):
  471. if self.isLocked():
  472. raise TypeError("Can not set attribute '%s': index is locked" % name)
  473. if name.startswith('_'):
  474. super().__setattr__(name, value)
  475. return
  476. self._index[name] = value
  477. def __delattr__(self, name):
  478. if self.isLocked():
  479. raise TypeError("Can not delete attribute '%s': index is locked" % name)
  480. if name.startswith('_'):
  481. super().__delattr__(name)
  482. self._index.pop(name)
  483. def lockData(self):
  484. '''Lock data object (make it readonly)'''
  485. super().__setattr__("_lock", True)
  486. def unlockData(self):
  487. '''unlock data object (make it readonly)'''
  488. super().__setattr__("_lock", False)
  489. # When the data is unlocked, we have to clear the caches, as
  490. # modification is allowed!
  491. del(self._layerBranches_layerId_branchId)
  492. del(self._layerDependencies_layerBranchId)
  493. del(self._layerBranches_vcsUrl)
  494. def isLocked(self):
  495. '''Is this object locked (readonly)?'''
  496. return self._lock
  497. def add_element(self, indexname, objs):
  498. '''Add a layer index object to index.<indexname>'''
  499. if indexname not in self._index:
  500. self._index[indexname] = {}
  501. for obj in objs:
  502. if obj.id in self._index[indexname]:
  503. if self._index[indexname][obj.id] == obj:
  504. continue
  505. raise LayerIndexError('Conflict adding object %s(%s) to index' % (indexname, obj.id))
  506. self._index[indexname][obj.id] = obj
  507. def add_raw_element(self, indexname, objtype, rawobjs):
  508. '''Convert a raw layer index data item to a layer index item object and add to the index'''
  509. objs = []
  510. for entry in rawobjs:
  511. objs.append(objtype(self, entry))
  512. self.add_element(indexname, objs)
  513. # Quick lookup table for searching layerId and branchID combos
  514. @property
  515. def layerBranches_layerId_branchId(self):
  516. def createCache(self):
  517. cache = {}
  518. for layerbranchid in self.layerBranches:
  519. layerbranch = self.layerBranches[layerbranchid]
  520. cache["%s:%s" % (layerbranch.layer_id, layerbranch.branch_id)] = layerbranch
  521. return cache
  522. if self.isLocked():
  523. cache = getattr(self, '_layerBranches_layerId_branchId', None)
  524. else:
  525. cache = None
  526. if not cache:
  527. cache = createCache(self)
  528. if self.isLocked():
  529. super().__setattr__('_layerBranches_layerId_branchId', cache)
  530. return cache
  531. # Quick lookup table for finding all dependencies of a layerBranch
  532. @property
  533. def layerDependencies_layerBranchId(self):
  534. def createCache(self):
  535. cache = {}
  536. # This ensures empty lists for all branchids
  537. for layerbranchid in self.layerBranches:
  538. cache[layerbranchid] = []
  539. for layerdependencyid in self.layerDependencies:
  540. layerdependency = self.layerDependencies[layerdependencyid]
  541. cache[layerdependency.layerbranch_id].append(layerdependency)
  542. return cache
  543. if self.isLocked():
  544. cache = getattr(self, '_layerDependencies_layerBranchId', None)
  545. else:
  546. cache = None
  547. if not cache:
  548. cache = createCache(self)
  549. if self.isLocked():
  550. super().__setattr__('_layerDependencies_layerBranchId', cache)
  551. return cache
  552. # Quick lookup table for finding all instances of a vcs_url
  553. @property
  554. def layerBranches_vcsUrl(self):
  555. def createCache(self):
  556. cache = {}
  557. for layerbranchid in self.layerBranches:
  558. layerbranch = self.layerBranches[layerbranchid]
  559. if layerbranch.layer.vcs_url not in cache:
  560. cache[layerbranch.layer.vcs_url] = [layerbranch]
  561. else:
  562. cache[layerbranch.layer.vcs_url].append(layerbranch)
  563. return cache
  564. if self.isLocked():
  565. cache = getattr(self, '_layerBranches_vcsUrl', None)
  566. else:
  567. cache = None
  568. if not cache:
  569. cache = createCache(self)
  570. if self.isLocked():
  571. super().__setattr__('_layerBranches_vcsUrl', cache)
  572. return cache
  573. def find_vcs_url(self, vcs_url, branches=None):
  574. ''''Return the first layerBranch with the given vcs_url
  575. If a list of branches has not been specified, we will iterate on
  576. all branches until the first vcs_url is found.'''
  577. if not self.__bool__():
  578. return None
  579. for layerbranch in self.layerBranches_vcsUrl:
  580. if branches and layerbranch.branch.name not in branches:
  581. continue
  582. return layerbranch
  583. return None
  584. def find_collection(self, collection, version=None, branches=None):
  585. '''Return the first layerBranch with the given collection name
  586. If a list of branches has not been specified, we will iterate on
  587. all branches until the first collection is found.'''
  588. if not self.__bool__():
  589. return None
  590. for layerbranchid in self.layerBranches:
  591. layerbranch = self.layerBranches[layerbranchid]
  592. if branches and layerbranch.branch.name not in branches:
  593. continue
  594. if layerbranch.collection == collection and \
  595. (version is None or version == layerbranch.version):
  596. return layerbranch
  597. return None
  598. def find_layerbranch(self, name, branches=None):
  599. '''Return the first layerbranch whose layer name matches
  600. If a list of branches has not been specified, we will iterate on
  601. all branches until the first layer with that name is found.'''
  602. if not self.__bool__():
  603. return None
  604. for layerbranchid in self.layerBranches:
  605. layerbranch = self.layerBranches[layerbranchid]
  606. if branches and layerbranch.branch.name not in branches:
  607. continue
  608. if layerbranch.layer.name == name:
  609. return layerbranch
  610. return None
  611. def find_dependencies(self, names=None, branches=None, layerBranches=None, ignores=None):
  612. '''Return a tuple of all dependencies and valid items for the list of (layer) names
  613. The dependency scanning happens depth-first. The returned
  614. dependencies should be in the best order to define bblayers.
  615. names - list of layer names (searching layerItems)
  616. branches - when specified (with names) only this list of branches are evaluated
  617. layerBranches - list of layerBranches to resolve dependencies
  618. ignores - list of layer names to ignore
  619. return: (dependencies, invalid)
  620. dependencies[LayerItem.name] = [ LayerBranch, LayerDependency1, LayerDependency2, ... ]
  621. invalid = [ LayerItem.name1, LayerItem.name2, ... ]'''
  622. invalid = []
  623. # Convert name/branch to layerBranches
  624. if layerbranches is None:
  625. layerbranches = []
  626. for name in names:
  627. if ignores and name in ignores:
  628. continue
  629. layerbranch = self.find_layerbranch(name, branches)
  630. if not layerbranch:
  631. invalid.append(name)
  632. else:
  633. layerbranches.append(layerbranch)
  634. for layerbranch in layerbranches:
  635. if layerbranch.index != self:
  636. raise LayerIndexException("Can not resolve dependencies across indexes with this class function!")
  637. def _resolve_dependencies(layerbranches, ignores, dependencies, invalid):
  638. for layerbranch in layerbranches:
  639. if ignores and layerBranch.layer.name in ignores:
  640. continue
  641. for layerdependency in layerbranch.index.layerDependencies_layerBranchId[layerBranch.id]:
  642. deplayerbranch = layerDependency.dependency_layerBranch
  643. if ignores and deplayerbranch.layer.name in ignores:
  644. continue
  645. # New dependency, we need to resolve it now... depth-first
  646. if deplayerbranch.layer.name not in dependencies:
  647. (dependencies, invalid) = _resolve_dependencies([deplayerbranch], ignores, dependencies, invalid)
  648. if deplayerbranch.layer.name not in dependencies:
  649. dependencies[deplayerbranch.layer.name] = [deplayerbranch, layerdependency]
  650. else:
  651. if layerdependency not in dependencies[deplayerbranch.layer.name]:
  652. dependencies[deplayerbranch.layer.name].append(layerdependency)
  653. return (dependencies, invalid)
  654. # OK, resolve this one...
  655. dependencies = OrderedDict()
  656. (dependencies, invalid) = _resolve_dependencies(layerbranches, ignores, dependencies, invalid)
  657. # Is this item already in the list, if not add it
  658. for layerbranch in layerbranches:
  659. if layerbranch.layer.name not in dependencies:
  660. dependencies[layerbranch.layer.name] = [layerbranch]
  661. return (dependencies, invalid)
  662. # Define a basic LayerIndexItemObj. This object forms the basis for all other
  663. # objects. The raw Layer Index data is stored in the _data element, but we
  664. # do not want users to access data directly. So wrap this and protect it
  665. # from direct manipulation.
  666. #
  667. # It is up to the insantiators of the objects to fill them out, and once done
  668. # lock the objects to prevent further accidently manipulation.
  669. #
  670. # Using the getattr, setattr and properties we can access and manipulate
  671. # the data within the data element.
  672. class LayerIndexItemObj():
  673. def __init__(self, index, data=None, lock=False):
  674. if data is None:
  675. data = {}
  676. if type(data) != type(dict()):
  677. raise TypeError('data (%s) is not a dict' % type(data))
  678. super().__setattr__('_lock', lock)
  679. super().__setattr__('index', index)
  680. super().__setattr__('_data', data)
  681. def __eq__(self, other):
  682. if self.__class__ != other.__class__:
  683. return False
  684. res=(self._data == other._data)
  685. return res
  686. def __bool__(self):
  687. return bool(self._data)
  688. def __getattr__(self, name):
  689. # These are internal to THIS class, and not part of data
  690. if name == "index" or name.startswith('_'):
  691. return super().__getattribute__(name)
  692. if name not in self._data:
  693. raise AttributeError('%s not in datastore' % name)
  694. return self._data[name]
  695. def _setattr(self, name, value, prop=True):
  696. '''__setattr__ like function, but with control over property object behavior'''
  697. if self.isLocked():
  698. raise TypeError("Can not set attribute '%s': Object data is locked" % name)
  699. if name.startswith('_'):
  700. super().__setattr__(name, value)
  701. return
  702. # Since __setattr__ runs before properties, we need to check if
  703. # there is a setter property and then execute it
  704. # ... or return self._data[name]
  705. propertyobj = getattr(self.__class__, name, None)
  706. if prop and isinstance(propertyobj, property):
  707. if propertyobj.fset:
  708. propertyobj.fset(self, value)
  709. else:
  710. raise AttributeError('Attribute %s is readonly, and may not be set' % name)
  711. else:
  712. self._data[name] = value
  713. def __setattr__(self, name, value):
  714. self._setattr(name, value, prop=True)
  715. def _delattr(self, name, prop=True):
  716. # Since __delattr__ runs before properties, we need to check if
  717. # there is a deleter property and then execute it
  718. # ... or we pop it ourselves..
  719. propertyobj = getattr(self.__class__, name, None)
  720. if prop and isinstance(propertyobj, property):
  721. if propertyobj.fdel:
  722. propertyobj.fdel(self)
  723. else:
  724. raise AttributeError('Attribute %s is readonly, and may not be deleted' % name)
  725. else:
  726. self._data.pop(name)
  727. def __delattr__(self, name):
  728. self._delattr(name, prop=True)
  729. def lockData(self):
  730. '''Lock data object (make it readonly)'''
  731. super().__setattr__("_lock", True)
  732. def unlockData(self):
  733. '''unlock data object (make it readonly)'''
  734. super().__setattr__("_lock", False)
  735. def isLocked(self):
  736. '''Is this object locked (readonly)?'''
  737. return self._lock
  738. # Branch object
  739. class Branch(LayerIndexItemObj):
  740. def define_data(self, id, name, bitbake_branch,
  741. short_description=None, sort_priority=1,
  742. updates_enabled=True, updated=None,
  743. update_environment=None):
  744. self.id = id
  745. self.name = name
  746. self.bitbake_branch = bitbake_branch
  747. self.short_description = short_description or name
  748. self.sort_priority = sort_priority
  749. self.updates_enabled = updates_enabled
  750. self.updated = updated or datetime.datetime.today().isoformat()
  751. self.update_environment = update_environment
  752. @property
  753. def name(self):
  754. return self.__getattr__('name')
  755. @name.setter
  756. def name(self, value):
  757. self._data['name'] = value
  758. if self.bitbake_branch == value:
  759. self.bitbake_branch = ""
  760. @name.deleter
  761. def name(self):
  762. self._delattr('name', prop=False)
  763. @property
  764. def bitbake_branch(self):
  765. try:
  766. return self.__getattr__('bitbake_branch')
  767. except AttributeError:
  768. return self.name
  769. @bitbake_branch.setter
  770. def bitbake_branch(self, value):
  771. if self.name == value:
  772. self._data['bitbake_branch'] = ""
  773. else:
  774. self._data['bitbake_branch'] = value
  775. @bitbake_branch.deleter
  776. def bitbake_branch(self):
  777. self._delattr('bitbake_branch', prop=False)
  778. class LayerItem(LayerIndexItemObj):
  779. def define_data(self, id, name, status='P',
  780. layer_type='A', summary=None,
  781. description=None,
  782. vcs_url=None, vcs_web_url=None,
  783. vcs_web_tree_base_url=None,
  784. vcs_web_file_base_url=None,
  785. usage_url=None,
  786. mailing_list_url=None,
  787. index_preference=1,
  788. classic=False,
  789. updated=None):
  790. self.id = id
  791. self.name = name
  792. self.status = status
  793. self.layer_type = layer_type
  794. self.summary = summary or name
  795. self.description = description or summary or name
  796. self.vcs_url = vcs_url
  797. self.vcs_web_url = vcs_web_url
  798. self.vcs_web_tree_base_url = vcs_web_tree_base_url
  799. self.vcs_web_file_base_url = vcs_web_file_base_url
  800. self.index_preference = index_preference
  801. self.classic = classic
  802. self.updated = updated or datetime.datetime.today().isoformat()
  803. class LayerBranch(LayerIndexItemObj):
  804. def define_data(self, id, collection, version, layer, branch,
  805. vcs_subdir="", vcs_last_fetch=None,
  806. vcs_last_rev=None, vcs_last_commit=None,
  807. actual_branch="",
  808. updated=None):
  809. self.id = id
  810. self.collection = collection
  811. self.version = version
  812. if type(layer) != type(LayerItem):
  813. self.layer_id = layer
  814. else:
  815. self.layer = layer
  816. if type(branch) != type(Branch):
  817. self.branch_id = branch
  818. else:
  819. self.branch = branch
  820. self.vcs_subdir = vcs_subdir
  821. self.vcs_last_fetch = vcs_last_fetch
  822. self.vcs_last_rev = vcs_last_rev
  823. self.vcs_last_commit = vcs_last_commit
  824. self.actual_branch = actual_branch
  825. self.updated = updated or datetime.datetime.today().isoformat()
  826. # This is a little odd, the _data attribute is 'layer', but it's really
  827. # referring to the layer id.. so lets adjust this to make it useful
  828. @property
  829. def layer_id(self):
  830. return self.__getattr__('layer')
  831. @layer_id.setter
  832. def layer_id(self, value):
  833. self._setattr('layer', value, prop=False)
  834. @layer_id.deleter
  835. def layer_id(self):
  836. self._delattr('layer', prop=False)
  837. @property
  838. def layer(self):
  839. try:
  840. return self.index.layerItems[self.layer_id]
  841. except KeyError:
  842. raise AttributeError('Unable to find layerItems in index to map layer_id %s' % self.layer_id)
  843. except IndexError:
  844. raise AttributeError('Unable to find layer_id %s in index layerItems' % self.layer_id)
  845. @layer.setter
  846. def layer(self, value):
  847. if type(value) != type(LayerItem):
  848. raise TypeError('value is not a LayerItem')
  849. if self.index != value.index:
  850. raise AttributeError('Object and value do not share the same index and thus key set.')
  851. self.layer_id = value.id
  852. @layer.deleter
  853. def layer(self):
  854. del self.layer_id
  855. @property
  856. def branch_id(self):
  857. return self.__getattr__('branch')
  858. @branch_id.setter
  859. def branch_id(self, value):
  860. self._setattr('branch', value, prop=False)
  861. @branch_id.deleter
  862. def branch_id(self):
  863. self._delattr('branch', prop=False)
  864. @property
  865. def branch(self):
  866. try:
  867. logger.debug(1, "Get branch object from branches[%s]" % (self.branch_id))
  868. return self.index.branches[self.branch_id]
  869. except KeyError:
  870. raise AttributeError('Unable to find branches in index to map branch_id %s' % self.branch_id)
  871. except IndexError:
  872. raise AttributeError('Unable to find branch_id %s in index branches' % self.branch_id)
  873. @branch.setter
  874. def branch(self, value):
  875. if type(value) != type(LayerItem):
  876. raise TypeError('value is not a LayerItem')
  877. if self.index != value.index:
  878. raise AttributeError('Object and value do not share the same index and thus key set.')
  879. self.branch_id = value.id
  880. @branch.deleter
  881. def branch(self):
  882. del self.branch_id
  883. @property
  884. def actual_branch(self):
  885. if self.__getattr__('actual_branch'):
  886. return self.__getattr__('actual_branch')
  887. else:
  888. return self.branch.name
  889. @actual_branch.setter
  890. def actual_branch(self, value):
  891. logger.debug(1, "Set actual_branch to %s .. name is %s" % (value, self.branch.name))
  892. if value != self.branch.name:
  893. self._setattr('actual_branch', value, prop=False)
  894. else:
  895. self._setattr('actual_branch', '', prop=False)
  896. @actual_branch.deleter
  897. def actual_branch(self):
  898. self._delattr('actual_branch', prop=False)
  899. # Extend LayerIndexItemObj with common LayerBranch manipulations
  900. # All of the remaining LayerIndex objects refer to layerbranch, and it is
  901. # up to the user to follow that back through the LayerBranch object into
  902. # the layer object to get various attributes. So add an intermediate set
  903. # of attributes that can easily get us the layerbranch as well as layer.
  904. class LayerIndexItemObj_LayerBranch(LayerIndexItemObj):
  905. @property
  906. def layerbranch_id(self):
  907. return self.__getattr__('layerbranch')
  908. @layerbranch_id.setter
  909. def layerbranch_id(self, value):
  910. self._setattr('layerbranch', value, prop=False)
  911. @layerbranch_id.deleter
  912. def layerbranch_id(self):
  913. self._delattr('layerbranch', prop=False)
  914. @property
  915. def layerbranch(self):
  916. try:
  917. return self.index.layerBranches[self.layerbranch_id]
  918. except KeyError:
  919. raise AttributeError('Unable to find layerBranches in index to map layerbranch_id %s' % self.layerbranch_id)
  920. except IndexError:
  921. raise AttributeError('Unable to find layerbranch_id %s in index branches' % self.layerbranch_id)
  922. @layerbranch.setter
  923. def layerbranch(self, value):
  924. if type(value) != type(LayerBranch):
  925. raise TypeError('value (%s) is not a layerBranch' % type(value))
  926. if self.index != value.index:
  927. raise AttributeError('Object and value do not share the same index and thus key set.')
  928. self.layerbranch_id = value.id
  929. @layerbranch.deleter
  930. def layerbranch(self):
  931. del self.layerbranch_id
  932. @property
  933. def layer_id(self):
  934. return self.layerbranch.layer_id
  935. # Doesn't make sense to set or delete layer_id
  936. @property
  937. def layer(self):
  938. return self.layerbranch.layer
  939. # Doesn't make sense to set or delete layer
  940. class LayerDependency(LayerIndexItemObj_LayerBranch):
  941. def define_data(self, id, layerbranch, dependency, required=True):
  942. self.id = id
  943. if type(layerbranch) != type(LayerBranch):
  944. self.layerbranch_id = layerbranch
  945. else:
  946. self.layerbranch = layerbranch
  947. if type(dependency) != type(LayerDependency):
  948. self.dependency_id = dependency
  949. else:
  950. self.dependency = dependency
  951. self.required = required
  952. @property
  953. def dependency_id(self):
  954. return self.__getattr__('dependency')
  955. @dependency_id.setter
  956. def dependency_id(self, value):
  957. self._setattr('dependency', value, prop=False)
  958. @dependency_id.deleter
  959. def dependency_id(self):
  960. self._delattr('dependency', prop=False)
  961. @property
  962. def dependency(self):
  963. try:
  964. return self.index.layerItems[self.dependency_id]
  965. except KeyError:
  966. raise AttributeError('Unable to find layerItems in index to map layerbranch_id %s' % self.dependency_id)
  967. except IndexError:
  968. raise AttributeError('Unable to find dependency_id %s in index layerItems' % self.dependency_id)
  969. @dependency.setter
  970. def dependency(self, value):
  971. if type(value) != type(LayerDependency):
  972. raise TypeError('value (%s) is not a dependency' % type(value))
  973. if self.index != value.index:
  974. raise AttributeError('Object and value do not share the same index and thus key set.')
  975. self.dependency_id = value.id
  976. @dependency.deleter
  977. def dependency(self):
  978. self._delattr('dependency', prop=False)
  979. @property
  980. def dependency_layerBranch(self):
  981. layerid = self.dependency_id
  982. branchid = self.layerbranch.branch_id
  983. try:
  984. return self.index.layerBranches_layerId_branchId["%s:%s" % (layerid, branchid)]
  985. except IndexError:
  986. # layerBranches_layerId_branchId -- but not layerId:branchId
  987. raise AttributeError('Unable to find layerId:branchId %s:%s in index layerBranches_layerId_branchId' % (layerid, branchid))
  988. except KeyError:
  989. raise AttributeError('Unable to find layerId:branchId %s:%s in layerItems and layerBranches' % (layerid, branchid))
  990. # dependency_layerBranch doesn't make sense to set or del
  991. class Recipe(LayerIndexItemObj_LayerBranch):
  992. def define_data(self, id,
  993. filename, filepath, pn, pv, layerbranch,
  994. summary="", description="", section="", license="",
  995. homepage="", bugtracker="", provides="", bbclassextend="",
  996. inherits="", blacklisted="", updated=None):
  997. self.id = id
  998. self.filename = filename
  999. self.filepath = filepath
  1000. self.pn = pn
  1001. self.pv = pv
  1002. self.summary = summary
  1003. self.description = description
  1004. self.section = section
  1005. self.license = license
  1006. self.homepage = homepage
  1007. self.bugtracker = bugtracker
  1008. self.provides = provides
  1009. self.bbclassextend = bbclassextend
  1010. self.inherits = inherits
  1011. self.updated = updated or datetime.datetime.today().isoformat()
  1012. self.blacklisted = blacklisted
  1013. if type(layerbranch) != type(LayerBranch):
  1014. self.layerbranch_id = layerbranch
  1015. else:
  1016. self.layerbranch = layerbranch
  1017. @property
  1018. def fullpath(self):
  1019. return os.path.join(self.filepath, self.filename)
  1020. # Set would need to understand how to split it
  1021. # del would we del both parts?
  1022. @property
  1023. def inherits(self):
  1024. if 'inherits' not in self._data:
  1025. # Older indexes may not have this, so emulate it
  1026. if '-image-' in self.pn:
  1027. return 'image'
  1028. return self.__getattr__('inherits')
  1029. @inherits.setter
  1030. def inherits(self, value):
  1031. return self._setattr('inherits', value, prop=False)
  1032. @inherits.deleter
  1033. def inherits(self):
  1034. return self._delattr('inherits', prop=False)
  1035. class Machine(LayerIndexItemObj_LayerBranch):
  1036. def define_data(self, id,
  1037. name, description, layerbranch,
  1038. updated=None):
  1039. self.id = id
  1040. self.name = name
  1041. self.description = description
  1042. if type(layerbranch) != type(LayerBranch):
  1043. self.layerbranch_id = layerbranch
  1044. else:
  1045. self.layerbranch = layerbranch
  1046. self.updated = updated or datetime.datetime.today().isoformat()
  1047. class Distro(LayerIndexItemObj_LayerBranch):
  1048. def define_data(self, id,
  1049. name, description, layerbranch,
  1050. updated=None):
  1051. self.id = id
  1052. self.name = name
  1053. self.description = description
  1054. if type(layerbranch) != type(LayerBranch):
  1055. self.layerbranch_id = layerbranch
  1056. else:
  1057. self.layerbranch = layerbranch
  1058. self.updated = updated or datetime.datetime.today().isoformat()
  1059. # When performing certain actions, we may need to sort the data.
  1060. # This will allow us to keep it consistent from run to run.
  1061. def sort_entry(item):
  1062. newitem = item
  1063. try:
  1064. if type(newitem) == type(dict()):
  1065. newitem = OrderedDict(sorted(newitem.items(), key=lambda t: t[0]))
  1066. for index in newitem:
  1067. newitem[index] = sort_entry(newitem[index])
  1068. elif type(newitem) == type(list()):
  1069. newitem.sort(key=lambda obj: obj['id'])
  1070. for index, _ in enumerate(newitem):
  1071. newitem[index] = sort_entry(newitem[index])
  1072. except:
  1073. logger.error('Sort failed for item %s' % type(item))
  1074. pass
  1075. return newitem