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