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