__init__.py 50 KB

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