__init__.py 51 KB

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