summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/layerindexlib
diff options
context:
space:
mode:
authorRichard Purdie <richard.purdie@linuxfoundation.org>2025-11-07 13:31:53 +0000
committerRichard Purdie <richard.purdie@linuxfoundation.org>2025-11-07 13:31:53 +0000
commit8c22ff0d8b70d9b12f0487ef696a7e915b9e3173 (patch)
treeefdc32587159d0050a69009bdf2330a531727d95 /bitbake/lib/layerindexlib
parentd412d2747595c1cc4a5e3ca975e3adc31b2f7891 (diff)
downloadpoky-8c22ff0d8b70d9b12f0487ef696a7e915b9e3173.tar.gz
The poky repository master branch is no longer being updated.
You can either: a) switch to individual clones of bitbake, openembedded-core, meta-yocto and yocto-docs b) use the new bitbake-setup You can find information about either approach in our documentation: https://docs.yoctoproject.org/ Note that "poky" the distro setting is still available in meta-yocto as before and we continue to use and maintain that. Long live Poky! Some further information on the background of this change can be found in: https://lists.openembedded.org/g/openembedded-architecture/message/2179 Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/layerindexlib')
-rw-r--r--bitbake/lib/layerindexlib/README28
-rw-r--r--bitbake/lib/layerindexlib/__init__.py1377
-rw-r--r--bitbake/lib/layerindexlib/cooker.py334
-rw-r--r--bitbake/lib/layerindexlib/plugin.py46
-rw-r--r--bitbake/lib/layerindexlib/restapi.py392
-rw-r--r--bitbake/lib/layerindexlib/tests/__init__.py0
-rw-r--r--bitbake/lib/layerindexlib/tests/common.py33
-rw-r--r--bitbake/lib/layerindexlib/tests/cooker.py110
-rw-r--r--bitbake/lib/layerindexlib/tests/layerindexobj.py210
-rw-r--r--bitbake/lib/layerindexlib/tests/restapi.py171
-rw-r--r--bitbake/lib/layerindexlib/tests/testdata/README11
-rw-r--r--bitbake/lib/layerindexlib/tests/testdata/build/conf/bblayers.conf15
-rw-r--r--bitbake/lib/layerindexlib/tests/testdata/layer1/conf/layer.conf17
-rw-r--r--bitbake/lib/layerindexlib/tests/testdata/layer2/conf/layer.conf20
-rw-r--r--bitbake/lib/layerindexlib/tests/testdata/layer3/conf/layer.conf19
-rw-r--r--bitbake/lib/layerindexlib/tests/testdata/layer4/conf/layer.conf22
16 files changed, 0 insertions, 2805 deletions
diff --git a/bitbake/lib/layerindexlib/README b/bitbake/lib/layerindexlib/README
deleted file mode 100644
index 5d927afdf7..0000000000
--- a/bitbake/lib/layerindexlib/README
+++ /dev/null
@@ -1,28 +0,0 @@
1The layerindexlib module is designed to permit programs to work directly
2with layer index information. (See layers.openembedded.org...)
3
4The layerindexlib module includes a plugin interface that is used to extend
5the basic functionality. There are two primary plugins available: restapi
6and cooker.
7
8The restapi plugin works with a web based REST Api compatible with the
9layerindex-web project, as well as the ability to store and retried a
10the information for one or more files on the disk.
11
12The cooker plugin works by reading the information from the current build
13project and processing it as if it were a layer index.
14
15
16TODO:
17
18__init__.py:
19Implement local on-disk caching (using the rest api store/load)
20Implement layer index style query operations on a combined index
21
22common.py:
23Stop network access if BB_NO_NETWORK or allowed hosts is restricted
24
25cooker.py:
26Cooker - Implement recipe parsing
27
28
diff --git a/bitbake/lib/layerindexlib/__init__.py b/bitbake/lib/layerindexlib/__init__.py
deleted file mode 100644
index c3265ddaa1..0000000000
--- a/bitbake/lib/layerindexlib/__init__.py
+++ /dev/null
@@ -1,1377 +0,0 @@
1# Copyright (C) 2016-2018 Wind River Systems, Inc.
2#
3# SPDX-License-Identifier: GPL-2.0-only
4#
5
6import datetime
7
8import logging
9import os
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 TypeError("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(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("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("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("HTTP Error: %s: %s" % (e.code, e.reason))
141 logger.debug(" Requested: %s" % (url))
142 logger.debug(" Actual: %s" % (e.geturl()))
143
144 if e.code == 404:
145 logger.debug("Request not found.")
146 raise LayerIndexFetchError(url, e)
147 else:
148 logger.debug("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("...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 https://layers.openembedded.org/layerindex/api/;branch=master;desc=OpenEmbedded%20Layer%20Index
202 cooker://
203'''
204 if reload:
205 self.indexes = []
206
207 logger.debug('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('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("%s doesn't support %s" % (plugin.type, e.url))
223 except NotImplementedError:
224 pass
225 else:
226 logger.debug("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('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("%s doesn't support %s" % (plugin.type, e.url))
265 except NotImplementedError:
266 logger.debug("Store not implemented in %s" % plugin.type)
267 pass
268 else:
269 logger.debug("No plugins support %s" % indexURI)
270 raise LayerIndexException("No plugins support %s" % indexURI)
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(' 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('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(' searching %s' % index.config['DESCRIPTION'])
315 layerBranch = index.find_collection(collection, version, branches)
316 if layerBranch:
317 return layerBranch
318 else:
319 logger.debug('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, processed=None):
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 try:
387 deplayerbranch = layerdependency.dependency_layerBranch
388 except AttributeError as e:
389 logger.error('LayerBranch does not exist for dependent layer {}:{}\n' \
390 ' Cannot continue successfully.\n' \
391 ' You might be able to resolve this by checking out the layer locally.\n' \
392 ' Consider reaching out the to the layer maintainers or the layerindex admins' \
393 .format(layerdependency.dependency.name, layerbranch.branch.name))
394
395 if ignores and deplayerbranch.layer.name in ignores:
396 continue
397
398 # Since this is depth first, we need to know what we're currently processing
399 # in order to avoid infinite recursion on a loop.
400 if processed and deplayerbranch.layer.name in processed:
401 # We have found a recursion...
402 logger.warning('Circular layer dependency found: %s -> %s' % (processed, deplayerbranch.layer.name))
403 continue
404
405 # This little block is why we can't re-use the LayerIndexObj version,
406 # we must be able to satisfy each dependencies across layer indexes and
407 # use the layer index order for priority. (r stands for replacement below)
408
409 # If this is the primary index, we can fast path and skip this
410 if deplayerbranch.index != self.indexes[0]:
411 # Is there an entry in a prior index for this collection/version?
412 rdeplayerbranch = self.find_collection(
413 collection=deplayerbranch.collection,
414 version=deplayerbranch.version
415 )
416 if rdeplayerbranch != deplayerbranch:
417 logger.debug('Replaced %s:%s:%s with %s:%s:%s' % \
418 (deplayerbranch.index.config['DESCRIPTION'],
419 deplayerbranch.branch.name,
420 deplayerbranch.layer.name,
421 rdeplayerbranch.index.config['DESCRIPTION'],
422 rdeplayerbranch.branch.name,
423 rdeplayerbranch.layer.name))
424 deplayerbranch = rdeplayerbranch
425
426 # New dependency, we need to resolve it now... depth-first
427 if deplayerbranch.layer.name not in dependencies:
428 # Avoid recursion on this branch.
429 # We copy so we don't end up polluting the depth-first branch with other
430 # branches. Duplication between individual branches IS expected and
431 # handled by 'dependencies' processing.
432 if not processed:
433 local_processed = []
434 else:
435 local_processed = processed.copy()
436 local_processed.append(deplayerbranch.layer.name)
437
438 (dependencies, invalid) = _resolve_dependencies([deplayerbranch], ignores, dependencies, invalid, local_processed)
439
440 if deplayerbranch.layer.name not in dependencies:
441 dependencies[deplayerbranch.layer.name] = [deplayerbranch, layerdependency]
442 else:
443 if layerdependency not in dependencies[deplayerbranch.layer.name]:
444 dependencies[deplayerbranch.layer.name].append(layerdependency)
445
446 return (dependencies, invalid)
447
448 # OK, resolve this one...
449 dependencies = OrderedDict()
450 (dependencies, invalid) = _resolve_dependencies(layerbranches, ignores, dependencies, invalid)
451
452 for layerbranch in layerbranches:
453 if layerbranch.layer.name not in dependencies:
454 dependencies[layerbranch.layer.name] = [layerbranch]
455
456 return (dependencies, invalid)
457
458
459 def list_obj(self, object):
460 '''Print via the plain logger object information
461
462This function is used to implement debugging and provide the user info.
463'''
464 for lix in self.indexes:
465 if not hasattr(lix, object):
466 continue
467
468 logger.plain ('')
469 logger.plain ('Index: %s' % lix.config['DESCRIPTION'])
470
471 output = []
472
473 if object == 'branches':
474 logger.plain ('%s %s %s' % ('{:26}'.format('branch'), '{:34}'.format('description'), '{:22}'.format('bitbake branch')))
475 logger.plain ('{:-^80}'.format(""))
476 for branchid in lix.branches:
477 output.append('%s %s %s' % (
478 '{:26}'.format(lix.branches[branchid].name),
479 '{:34}'.format(lix.branches[branchid].short_description),
480 '{:22}'.format(lix.branches[branchid].bitbake_branch)
481 ))
482 for line in sorted(output):
483 logger.plain (line)
484
485 continue
486
487 if object == 'layerItems':
488 logger.plain ('%s %s' % ('{:26}'.format('layer'), '{:34}'.format('description')))
489 logger.plain ('{:-^80}'.format(""))
490 for layerid in lix.layerItems:
491 output.append('%s %s' % (
492 '{:26}'.format(lix.layerItems[layerid].name),
493 '{:34}'.format(lix.layerItems[layerid].summary)
494 ))
495 for line in sorted(output):
496 logger.plain (line)
497
498 continue
499
500 if object == 'layerBranches':
501 logger.plain ('%s %s %s' % ('{:26}'.format('layer'), '{:34}'.format('description'), '{:19}'.format('collection:version')))
502 logger.plain ('{:-^80}'.format(""))
503 for layerbranchid in lix.layerBranches:
504 output.append('%s %s %s' % (
505 '{:26}'.format(lix.layerBranches[layerbranchid].layer.name),
506 '{:34}'.format(lix.layerBranches[layerbranchid].layer.summary),
507 '{:19}'.format("%s:%s" %
508 (lix.layerBranches[layerbranchid].collection,
509 lix.layerBranches[layerbranchid].version)
510 )
511 ))
512 for line in sorted(output):
513 logger.plain (line)
514
515 continue
516
517 if object == 'layerDependencies':
518 logger.plain ('%s %s %s %s' % ('{:19}'.format('branch'), '{:26}'.format('layer'), '{:11}'.format('dependency'), '{:26}'.format('layer')))
519 logger.plain ('{:-^80}'.format(""))
520 for layerDependency in lix.layerDependencies:
521 if not lix.layerDependencies[layerDependency].dependency_layerBranch:
522 continue
523
524 output.append('%s %s %s %s' % (
525 '{:19}'.format(lix.layerDependencies[layerDependency].layerbranch.branch.name),
526 '{:26}'.format(lix.layerDependencies[layerDependency].layerbranch.layer.name),
527 '{:11}'.format('requires' if lix.layerDependencies[layerDependency].required else 'recommends'),
528 '{:26}'.format(lix.layerDependencies[layerDependency].dependency_layerBranch.layer.name)
529 ))
530 for line in sorted(output):
531 logger.plain (line)
532
533 continue
534
535 if object == 'recipes':
536 logger.plain ('%s %s %s' % ('{:20}'.format('recipe'), '{:10}'.format('version'), 'layer'))
537 logger.plain ('{:-^80}'.format(""))
538 output = []
539 for recipe in lix.recipes:
540 output.append('%s %s %s' % (
541 '{:30}'.format(lix.recipes[recipe].pn),
542 '{:30}'.format(lix.recipes[recipe].pv),
543 lix.recipes[recipe].layer.name
544 ))
545 for line in sorted(output):
546 logger.plain (line)
547
548 continue
549
550 if object == 'machines':
551 logger.plain ('%s %s %s' % ('{:24}'.format('machine'), '{:34}'.format('description'), '{:19}'.format('layer')))
552 logger.plain ('{:-^80}'.format(""))
553 for machine in lix.machines:
554 output.append('%s %s %s' % (
555 '{:24}'.format(lix.machines[machine].name),
556 '{:34}'.format(lix.machines[machine].description)[:34],
557 '{:19}'.format(lix.machines[machine].layerbranch.layer.name)
558 ))
559 for line in sorted(output):
560 logger.plain (line)
561
562 continue
563
564 if object == 'distros':
565 logger.plain ('%s %s %s' % ('{:24}'.format('distro'), '{:34}'.format('description'), '{:19}'.format('layer')))
566 logger.plain ('{:-^80}'.format(""))
567 for distro in lix.distros:
568 output.append('%s %s %s' % (
569 '{:24}'.format(lix.distros[distro].name),
570 '{:34}'.format(lix.distros[distro].description)[:34],
571 '{:19}'.format(lix.distros[distro].layerbranch.layer.name)
572 ))
573 for line in sorted(output):
574 logger.plain (line)
575
576 continue
577
578 logger.plain ('')
579
580
581# This class holds a single layer index instance
582# The LayerIndexObj is made up of dictionary of elements, such as:
583# index['config'] - configuration data for this index
584# index['branches'] - dictionary of Branch objects, by id number
585# index['layerItems'] - dictionary of layerItem objects, by id number
586# ...etc... (See: https://layers.openembedded.org/layerindex/api/)
587#
588# The class needs to manage the 'index' entries and allow easily adding
589# of new items, as well as simply loading of the items.
590class LayerIndexObj():
591 def __init__(self):
592 super().__setattr__('_index', {})
593 super().__setattr__('_lock', False)
594
595 def __bool__(self):
596 '''False if the index is effectively empty
597
598 We check the index to see if it has a branch set, as well as
599 layerbranches set. If not, it is effectively blank.'''
600
601 if not bool(self._index):
602 return False
603
604 try:
605 if self.branches and self.layerBranches:
606 return True
607 except AttributeError:
608 pass
609
610 return False
611
612 def __getattr__(self, name):
613 if name.startswith('_'):
614 return super().__getattribute__(name)
615
616 if name not in self._index:
617 raise AttributeError('%s not in index datastore' % name)
618
619 return self._index[name]
620
621 def __setattr__(self, name, value):
622 if self.isLocked():
623 raise TypeError("Can not set attribute '%s': index is locked" % name)
624
625 if name.startswith('_'):
626 super().__setattr__(name, value)
627 return
628
629 self._index[name] = value
630
631 def __delattr__(self, name):
632 if self.isLocked():
633 raise TypeError("Can not delete attribute '%s': index is locked" % name)
634
635 if name.startswith('_'):
636 super().__delattr__(name)
637
638 self._index.pop(name)
639
640 def lockData(self):
641 '''Lock data object (make it readonly)'''
642 super().__setattr__("_lock", True)
643
644 def unlockData(self):
645 '''unlock data object (make it readonly)'''
646 super().__setattr__("_lock", False)
647
648 # When the data is unlocked, we have to clear the caches, as
649 # modification is allowed!
650 del(self._layerBranches_layerId_branchId)
651 del(self._layerDependencies_layerBranchId)
652 del(self._layerBranches_vcsUrl)
653
654 def isLocked(self):
655 '''Is this object locked (readonly)?'''
656 return self._lock
657
658 def add_element(self, indexname, objs):
659 '''Add a layer index object to index.<indexname>'''
660 if indexname not in self._index:
661 self._index[indexname] = {}
662
663 for obj in objs:
664 if obj.id in self._index[indexname]:
665 if self._index[indexname][obj.id] == obj:
666 continue
667 raise LayerIndexException('Conflict adding object %s(%s) to index' % (indexname, obj.id))
668 self._index[indexname][obj.id] = obj
669
670 def add_raw_element(self, indexname, objtype, rawobjs):
671 '''Convert a raw layer index data item to a layer index item object and add to the index'''
672 objs = []
673 for entry in rawobjs:
674 objs.append(objtype(self, entry))
675 self.add_element(indexname, objs)
676
677 # Quick lookup table for searching layerId and branchID combos
678 @property
679 def layerBranches_layerId_branchId(self):
680 def createCache(self):
681 cache = {}
682 for layerbranchid in self.layerBranches:
683 layerbranch = self.layerBranches[layerbranchid]
684 cache["%s:%s" % (layerbranch.layer_id, layerbranch.branch_id)] = layerbranch
685 return cache
686
687 if self.isLocked():
688 cache = getattr(self, '_layerBranches_layerId_branchId', None)
689 else:
690 cache = None
691
692 if not cache:
693 cache = createCache(self)
694
695 if self.isLocked():
696 super().__setattr__('_layerBranches_layerId_branchId', cache)
697
698 return cache
699
700 # Quick lookup table for finding all dependencies of a layerBranch
701 @property
702 def layerDependencies_layerBranchId(self):
703 def createCache(self):
704 cache = {}
705 # This ensures empty lists for all branchids
706 for layerbranchid in self.layerBranches:
707 cache[layerbranchid] = []
708
709 for layerdependencyid in self.layerDependencies:
710 layerdependency = self.layerDependencies[layerdependencyid]
711 cache[layerdependency.layerbranch_id].append(layerdependency)
712 return cache
713
714 if self.isLocked():
715 cache = getattr(self, '_layerDependencies_layerBranchId', None)
716 else:
717 cache = None
718
719 if not cache:
720 cache = createCache(self)
721
722 if self.isLocked():
723 super().__setattr__('_layerDependencies_layerBranchId', cache)
724
725 return cache
726
727 # Quick lookup table for finding all instances of a vcs_url
728 @property
729 def layerBranches_vcsUrl(self):
730 def createCache(self):
731 cache = {}
732 for layerbranchid in self.layerBranches:
733 layerbranch = self.layerBranches[layerbranchid]
734 if layerbranch.layer.vcs_url not in cache:
735 cache[layerbranch.layer.vcs_url] = [layerbranch]
736 else:
737 cache[layerbranch.layer.vcs_url].append(layerbranch)
738 return cache
739
740 if self.isLocked():
741 cache = getattr(self, '_layerBranches_vcsUrl', None)
742 else:
743 cache = None
744
745 if not cache:
746 cache = createCache(self)
747
748 if self.isLocked():
749 super().__setattr__('_layerBranches_vcsUrl', cache)
750
751 return cache
752
753
754 def find_vcs_url(self, vcs_url, branches=None):
755 ''''Return the first layerBranch with the given vcs_url
756
757 If a list of branches has not been specified, we will iterate on
758 all branches until the first vcs_url is found.'''
759
760 if not self.__bool__():
761 return None
762
763 for layerbranch in self.layerBranches_vcsUrl:
764 if branches and layerbranch.branch.name not in branches:
765 continue
766
767 return layerbranch
768
769 return None
770
771
772 def find_collection(self, collection, version=None, branches=None):
773 '''Return the first layerBranch with the given collection name
774
775 If a list of branches has not been specified, we will iterate on
776 all branches until the first collection is found.'''
777
778 if not self.__bool__():
779 return None
780
781 for layerbranchid in self.layerBranches:
782 layerbranch = self.layerBranches[layerbranchid]
783 if branches and layerbranch.branch.name not in branches:
784 continue
785
786 if layerbranch.collection == collection and \
787 (version is None or version == layerbranch.version):
788 return layerbranch
789
790 return None
791
792
793 def find_layerbranch(self, name, branches=None):
794 '''Return the first layerbranch whose layer name matches
795
796 If a list of branches has not been specified, we will iterate on
797 all branches until the first layer with that name is found.'''
798
799 if not self.__bool__():
800 return None
801
802 for layerbranchid in self.layerBranches:
803 layerbranch = self.layerBranches[layerbranchid]
804 if branches and layerbranch.branch.name not in branches:
805 continue
806
807 if layerbranch.layer.name == name:
808 return layerbranch
809
810 return None
811
812 def find_dependencies(self, names=None, branches=None, layerBranches=None, ignores=None):
813 '''Return a tuple of all dependencies and valid items for the list of (layer) names
814
815 The dependency scanning happens depth-first. The returned
816 dependencies should be in the best order to define bblayers.
817
818 names - list of layer names (searching layerItems)
819 branches - when specified (with names) only this list of branches are evaluated
820
821 layerBranches - list of layerBranches to resolve dependencies
822
823 ignores - list of layer names to ignore
824
825 return: (dependencies, invalid)
826
827 dependencies[LayerItem.name] = [ LayerBranch, LayerDependency1, LayerDependency2, ... ]
828 invalid = [ LayerItem.name1, LayerItem.name2, ... ]'''
829
830 invalid = []
831
832 # Convert name/branch to layerBranches
833 if layerbranches is None:
834 layerbranches = []
835
836 for name in names:
837 if ignores and name in ignores:
838 continue
839
840 layerbranch = self.find_layerbranch(name, branches)
841 if not layerbranch:
842 invalid.append(name)
843 else:
844 layerbranches.append(layerbranch)
845
846 for layerbranch in layerbranches:
847 if layerbranch.index != self:
848 raise LayerIndexException("Can not resolve dependencies across indexes with this class function!")
849
850 def _resolve_dependencies(layerbranches, ignores, dependencies, invalid):
851 for layerbranch in layerbranches:
852 if ignores and layerbranch.layer.name in ignores:
853 continue
854
855 for layerdependency in layerbranch.index.layerDependencies_layerBranchId[layerbranch.id]:
856 deplayerbranch = layerdependency.dependency_layerBranch or None
857
858 if ignores and deplayerbranch.layer.name in ignores:
859 continue
860
861 # New dependency, we need to resolve it now... depth-first
862 if deplayerbranch.layer.name not in dependencies:
863 (dependencies, invalid) = _resolve_dependencies([deplayerbranch], ignores, dependencies, invalid)
864
865 if deplayerbranch.layer.name not in dependencies:
866 dependencies[deplayerbranch.layer.name] = [deplayerbranch, layerdependency]
867 else:
868 if layerdependency not in dependencies[deplayerbranch.layer.name]:
869 dependencies[deplayerbranch.layer.name].append(layerdependency)
870
871 return (dependencies, invalid)
872
873 # OK, resolve this one...
874 dependencies = OrderedDict()
875 (dependencies, invalid) = _resolve_dependencies(layerbranches, ignores, dependencies, invalid)
876
877 # Is this item already in the list, if not add it
878 for layerbranch in layerbranches:
879 if layerbranch.layer.name not in dependencies:
880 dependencies[layerbranch.layer.name] = [layerbranch]
881
882 return (dependencies, invalid)
883
884
885# Define a basic LayerIndexItemObj. This object forms the basis for all other
886# objects. The raw Layer Index data is stored in the _data element, but we
887# do not want users to access data directly. So wrap this and protect it
888# from direct manipulation.
889#
890# It is up to the insantiators of the objects to fill them out, and once done
891# lock the objects to prevent further accidently manipulation.
892#
893# Using the getattr, setattr and properties we can access and manipulate
894# the data within the data element.
895class LayerIndexItemObj():
896 def __init__(self, index, data=None, lock=False):
897 if data is None:
898 data = {}
899
900 if type(data) != type(dict()):
901 raise TypeError('data (%s) is not a dict' % type(data))
902
903 super().__setattr__('_lock', lock)
904 super().__setattr__('index', index)
905 super().__setattr__('_data', data)
906
907 def __eq__(self, other):
908 if self.__class__ != other.__class__:
909 return False
910 res=(self._data == other._data)
911 return res
912
913 def __bool__(self):
914 return bool(self._data)
915
916 def __getattr__(self, name):
917 # These are internal to THIS class, and not part of data
918 if name == "index" or name.startswith('_'):
919 return super().__getattribute__(name)
920
921 if name not in self._data:
922 raise AttributeError('%s not in datastore' % name)
923
924 return self._data[name]
925
926 def _setattr(self, name, value, prop=True):
927 '''__setattr__ like function, but with control over property object behavior'''
928 if self.isLocked():
929 raise TypeError("Can not set attribute '%s': Object data is locked" % name)
930
931 if name.startswith('_'):
932 super().__setattr__(name, value)
933 return
934
935 # Since __setattr__ runs before properties, we need to check if
936 # there is a setter property and then execute it
937 # ... or return self._data[name]
938 propertyobj = getattr(self.__class__, name, None)
939 if prop and isinstance(propertyobj, property):
940 if propertyobj.fset:
941 propertyobj.fset(self, value)
942 else:
943 raise AttributeError('Attribute %s is readonly, and may not be set' % name)
944 else:
945 self._data[name] = value
946
947 def __setattr__(self, name, value):
948 self._setattr(name, value, prop=True)
949
950 def _delattr(self, name, prop=True):
951 # Since __delattr__ runs before properties, we need to check if
952 # there is a deleter property and then execute it
953 # ... or we pop it ourselves..
954 propertyobj = getattr(self.__class__, name, None)
955 if prop and isinstance(propertyobj, property):
956 if propertyobj.fdel:
957 propertyobj.fdel(self)
958 else:
959 raise AttributeError('Attribute %s is readonly, and may not be deleted' % name)
960 else:
961 self._data.pop(name)
962
963 def __delattr__(self, name):
964 self._delattr(name, prop=True)
965
966 def lockData(self):
967 '''Lock data object (make it readonly)'''
968 super().__setattr__("_lock", True)
969
970 def unlockData(self):
971 '''unlock data object (make it readonly)'''
972 super().__setattr__("_lock", False)
973
974 def isLocked(self):
975 '''Is this object locked (readonly)?'''
976 return self._lock
977
978# Branch object
979class Branch(LayerIndexItemObj):
980 def define_data(self, id, name, bitbake_branch,
981 short_description=None, sort_priority=1,
982 updates_enabled=True, updated=None,
983 update_environment=None):
984 self.id = id
985 self.name = name
986 self.bitbake_branch = bitbake_branch
987 self.short_description = short_description or name
988 self.sort_priority = sort_priority
989 self.updates_enabled = updates_enabled
990 self.updated = updated or datetime.datetime.today().isoformat()
991 self.update_environment = update_environment
992
993 @property
994 def name(self):
995 return self.__getattr__('name')
996
997 @name.setter
998 def name(self, value):
999 self._data['name'] = value
1000
1001 if self.bitbake_branch == value:
1002 self.bitbake_branch = ""
1003
1004 @name.deleter
1005 def name(self):
1006 self._delattr('name', prop=False)
1007
1008 @property
1009 def bitbake_branch(self):
1010 try:
1011 return self.__getattr__('bitbake_branch')
1012 except AttributeError:
1013 return self.name
1014
1015 @bitbake_branch.setter
1016 def bitbake_branch(self, value):
1017 if self.name == value:
1018 self._data['bitbake_branch'] = ""
1019 else:
1020 self._data['bitbake_branch'] = value
1021
1022 @bitbake_branch.deleter
1023 def bitbake_branch(self):
1024 self._delattr('bitbake_branch', prop=False)
1025
1026
1027class LayerItem(LayerIndexItemObj):
1028 def define_data(self, id, name, status='P',
1029 layer_type='A', summary=None,
1030 description=None,
1031 vcs_url=None, vcs_web_url=None,
1032 vcs_web_tree_base_url=None,
1033 vcs_web_file_base_url=None,
1034 usage_url=None,
1035 mailing_list_url=None,
1036 index_preference=1,
1037 classic=False,
1038 updated=None):
1039 self.id = id
1040 self.name = name
1041 self.status = status
1042 self.layer_type = layer_type
1043 self.summary = summary or name
1044 self.description = description or summary or name
1045 self.vcs_url = vcs_url
1046 self.vcs_web_url = vcs_web_url
1047 self.vcs_web_tree_base_url = vcs_web_tree_base_url
1048 self.vcs_web_file_base_url = vcs_web_file_base_url
1049 self.index_preference = index_preference
1050 self.classic = classic
1051 self.updated = updated or datetime.datetime.today().isoformat()
1052
1053
1054class LayerBranch(LayerIndexItemObj):
1055 def define_data(self, id, collection, version, layer, branch,
1056 vcs_subdir="", vcs_last_fetch=None,
1057 vcs_last_rev=None, vcs_last_commit=None,
1058 actual_branch="",
1059 updated=None):
1060 self.id = id
1061 self.collection = collection
1062 self.version = version
1063 if isinstance(layer, LayerItem):
1064 self.layer = layer
1065 else:
1066 self.layer_id = layer
1067
1068 if isinstance(branch, Branch):
1069 self.branch = branch
1070 else:
1071 self.branch_id = branch
1072
1073 self.vcs_subdir = vcs_subdir
1074 self.vcs_last_fetch = vcs_last_fetch
1075 self.vcs_last_rev = vcs_last_rev
1076 self.vcs_last_commit = vcs_last_commit
1077 self.actual_branch = actual_branch
1078 self.updated = updated or datetime.datetime.today().isoformat()
1079
1080 # This is a little odd, the _data attribute is 'layer', but it's really
1081 # referring to the layer id.. so lets adjust this to make it useful
1082 @property
1083 def layer_id(self):
1084 return self.__getattr__('layer')
1085
1086 @layer_id.setter
1087 def layer_id(self, value):
1088 self._setattr('layer', value, prop=False)
1089
1090 @layer_id.deleter
1091 def layer_id(self):
1092 self._delattr('layer', prop=False)
1093
1094 @property
1095 def layer(self):
1096 try:
1097 return self.index.layerItems[self.layer_id]
1098 except KeyError:
1099 raise AttributeError('Unable to find layerItems in index to map layer_id %s' % self.layer_id)
1100 except IndexError:
1101 raise AttributeError('Unable to find layer_id %s in index layerItems' % self.layer_id)
1102
1103 @layer.setter
1104 def layer(self, value):
1105 if not isinstance(value, LayerItem):
1106 raise TypeError('value is not a LayerItem')
1107 if self.index != value.index:
1108 raise AttributeError('Object and value do not share the same index and thus key set.')
1109 self.layer_id = value.id
1110
1111 @layer.deleter
1112 def layer(self):
1113 del self.layer_id
1114
1115 @property
1116 def branch_id(self):
1117 return self.__getattr__('branch')
1118
1119 @branch_id.setter
1120 def branch_id(self, value):
1121 self._setattr('branch', value, prop=False)
1122
1123 @branch_id.deleter
1124 def branch_id(self):
1125 self._delattr('branch', prop=False)
1126
1127 @property
1128 def branch(self):
1129 try:
1130 logger.debug("Get branch object from branches[%s]" % (self.branch_id))
1131 return self.index.branches[self.branch_id]
1132 except KeyError:
1133 raise AttributeError('Unable to find branches in index to map branch_id %s' % self.branch_id)
1134 except IndexError:
1135 raise AttributeError('Unable to find branch_id %s in index branches' % self.branch_id)
1136
1137 @branch.setter
1138 def branch(self, value):
1139 if not isinstance(value, LayerItem):
1140 raise TypeError('value is not a LayerItem')
1141 if self.index != value.index:
1142 raise AttributeError('Object and value do not share the same index and thus key set.')
1143 self.branch_id = value.id
1144
1145 @branch.deleter
1146 def branch(self):
1147 del self.branch_id
1148
1149 @property
1150 def actual_branch(self):
1151 if self.__getattr__('actual_branch'):
1152 return self.__getattr__('actual_branch')
1153 else:
1154 return self.branch.name
1155
1156 @actual_branch.setter
1157 def actual_branch(self, value):
1158 logger.debug("Set actual_branch to %s .. name is %s" % (value, self.branch.name))
1159 if value != self.branch.name:
1160 self._setattr('actual_branch', value, prop=False)
1161 else:
1162 self._setattr('actual_branch', '', prop=False)
1163
1164 @actual_branch.deleter
1165 def actual_branch(self):
1166 self._delattr('actual_branch', prop=False)
1167
1168# Extend LayerIndexItemObj with common LayerBranch manipulations
1169# All of the remaining LayerIndex objects refer to layerbranch, and it is
1170# up to the user to follow that back through the LayerBranch object into
1171# the layer object to get various attributes. So add an intermediate set
1172# of attributes that can easily get us the layerbranch as well as layer.
1173
1174class LayerIndexItemObj_LayerBranch(LayerIndexItemObj):
1175 @property
1176 def layerbranch_id(self):
1177 return self.__getattr__('layerbranch')
1178
1179 @layerbranch_id.setter
1180 def layerbranch_id(self, value):
1181 self._setattr('layerbranch', value, prop=False)
1182
1183 @layerbranch_id.deleter
1184 def layerbranch_id(self):
1185 self._delattr('layerbranch', prop=False)
1186
1187 @property
1188 def layerbranch(self):
1189 try:
1190 return self.index.layerBranches[self.layerbranch_id]
1191 except KeyError:
1192 raise AttributeError('Unable to find layerBranches in index to map layerbranch_id %s' % self.layerbranch_id)
1193 except IndexError:
1194 raise AttributeError('Unable to find layerbranch_id %s in index branches' % self.layerbranch_id)
1195
1196 @layerbranch.setter
1197 def layerbranch(self, value):
1198 if not isinstance(value, LayerBranch):
1199 raise TypeError('value (%s) is not a layerBranch' % type(value))
1200 if self.index != value.index:
1201 raise AttributeError('Object and value do not share the same index and thus key set.')
1202 self.layerbranch_id = value.id
1203
1204 @layerbranch.deleter
1205 def layerbranch(self):
1206 del self.layerbranch_id
1207
1208 @property
1209 def layer_id(self):
1210 return self.layerbranch.layer_id
1211
1212 # Doesn't make sense to set or delete layer_id
1213
1214 @property
1215 def layer(self):
1216 return self.layerbranch.layer
1217
1218 # Doesn't make sense to set or delete layer
1219
1220
1221class LayerDependency(LayerIndexItemObj_LayerBranch):
1222 def define_data(self, id, layerbranch, dependency, required=True):
1223 self.id = id
1224 if isinstance(layerbranch, LayerBranch):
1225 self.layerbranch = layerbranch
1226 else:
1227 self.layerbranch_id = layerbranch
1228 if isinstance(dependency, LayerDependency):
1229 self.dependency = dependency
1230 else:
1231 self.dependency_id = dependency
1232 self.required = required
1233
1234 @property
1235 def dependency_id(self):
1236 return self.__getattr__('dependency')
1237
1238 @dependency_id.setter
1239 def dependency_id(self, value):
1240 self._setattr('dependency', value, prop=False)
1241
1242 @dependency_id.deleter
1243 def dependency_id(self):
1244 self._delattr('dependency', prop=False)
1245
1246 @property
1247 def dependency(self):
1248 try:
1249 return self.index.layerItems[self.dependency_id]
1250 except KeyError:
1251 raise AttributeError('Unable to find layerItems in index to map layerbranch_id %s' % self.dependency_id)
1252 except IndexError:
1253 raise AttributeError('Unable to find dependency_id %s in index layerItems' % self.dependency_id)
1254
1255 @dependency.setter
1256 def dependency(self, value):
1257 if not isinstance(value, LayerDependency):
1258 raise TypeError('value (%s) is not a dependency' % type(value))
1259 if self.index != value.index:
1260 raise AttributeError('Object and value do not share the same index and thus key set.')
1261 self.dependency_id = value.id
1262
1263 @dependency.deleter
1264 def dependency(self):
1265 self._delattr('dependency', prop=False)
1266
1267 @property
1268 def dependency_layerBranch(self):
1269 layerid = self.dependency_id
1270 branchid = self.layerbranch.branch_id
1271
1272 try:
1273 return self.index.layerBranches_layerId_branchId["%s:%s" % (layerid, branchid)]
1274 except IndexError:
1275 # layerBranches_layerId_branchId -- but not layerId:branchId
1276 raise AttributeError('Unable to find layerId:branchId %s:%s in index layerBranches_layerId_branchId' % (layerid, branchid))
1277 except KeyError:
1278 raise AttributeError('Unable to find layerId:branchId %s:%s in layerItems and layerBranches' % (layerid, branchid))
1279
1280 # dependency_layerBranch doesn't make sense to set or del
1281
1282
1283class Recipe(LayerIndexItemObj_LayerBranch):
1284 def define_data(self, id,
1285 filename, filepath, pn, pv, layerbranch,
1286 summary="", description="", section="", license="",
1287 homepage="", bugtracker="", provides="", bbclassextend="",
1288 inherits="", disallowed="", updated=None):
1289 self.id = id
1290 self.filename = filename
1291 self.filepath = filepath
1292 self.pn = pn
1293 self.pv = pv
1294 self.summary = summary
1295 self.description = description
1296 self.section = section
1297 self.license = license
1298 self.homepage = homepage
1299 self.bugtracker = bugtracker
1300 self.provides = provides
1301 self.bbclassextend = bbclassextend
1302 self.inherits = inherits
1303 self.updated = updated or datetime.datetime.today().isoformat()
1304 self.disallowed = disallowed
1305 if isinstance(layerbranch, LayerBranch):
1306 self.layerbranch = layerbranch
1307 else:
1308 self.layerbranch_id = layerbranch
1309
1310 @property
1311 def fullpath(self):
1312 return os.path.join(self.filepath, self.filename)
1313
1314 # Set would need to understand how to split it
1315 # del would we del both parts?
1316
1317 @property
1318 def inherits(self):
1319 if 'inherits' not in self._data:
1320 # Older indexes may not have this, so emulate it
1321 if '-image-' in self.pn:
1322 return 'image'
1323 return self.__getattr__('inherits')
1324
1325 @inherits.setter
1326 def inherits(self, value):
1327 return self._setattr('inherits', value, prop=False)
1328
1329 @inherits.deleter
1330 def inherits(self):
1331 return self._delattr('inherits', prop=False)
1332
1333
1334class Machine(LayerIndexItemObj_LayerBranch):
1335 def define_data(self, id,
1336 name, description, layerbranch,
1337 updated=None):
1338 self.id = id
1339 self.name = name
1340 self.description = description
1341 if isinstance(layerbranch, LayerBranch):
1342 self.layerbranch = layerbranch
1343 else:
1344 self.layerbranch_id = layerbranch
1345 self.updated = updated or datetime.datetime.today().isoformat()
1346
1347class Distro(LayerIndexItemObj_LayerBranch):
1348 def define_data(self, id,
1349 name, description, layerbranch,
1350 updated=None):
1351 self.id = id
1352 self.name = name
1353 self.description = description
1354 if isinstance(layerbranch, LayerBranch):
1355 self.layerbranch = layerbranch
1356 else:
1357 self.layerbranch_id = layerbranch
1358 self.updated = updated or datetime.datetime.today().isoformat()
1359
1360# When performing certain actions, we may need to sort the data.
1361# This will allow us to keep it consistent from run to run.
1362def sort_entry(item):
1363 newitem = item
1364 try:
1365 if type(newitem) == type(dict()):
1366 newitem = OrderedDict(sorted(newitem.items(), key=lambda t: t[0]))
1367 for index in newitem:
1368 newitem[index] = sort_entry(newitem[index])
1369 elif type(newitem) == type(list()):
1370 newitem.sort(key=lambda obj: obj['id'])
1371 for index, _ in enumerate(newitem):
1372 newitem[index] = sort_entry(newitem[index])
1373 except:
1374 logger.error('Sort failed for item %s' % type(item))
1375 pass
1376
1377 return newitem
diff --git a/bitbake/lib/layerindexlib/cooker.py b/bitbake/lib/layerindexlib/cooker.py
deleted file mode 100644
index ced3e06360..0000000000
--- a/bitbake/lib/layerindexlib/cooker.py
+++ /dev/null
@@ -1,334 +0,0 @@
1# Copyright (C) 2016-2018 Wind River Systems, Inc.
2#
3# SPDX-License-Identifier: GPL-2.0-only
4#
5
6import logging
7import os
8
9from collections import defaultdict
10
11from urllib.parse import unquote, urlparse
12
13import layerindexlib
14
15import layerindexlib.plugin
16
17logger = logging.getLogger('BitBake.layerindexlib.cooker')
18
19import bb.utils
20
21def plugin_init(plugins):
22 return CookerPlugin()
23
24class CookerPlugin(layerindexlib.plugin.IndexPlugin):
25 def __init__(self):
26 self.type = "cooker"
27
28 self.server_connection = None
29 self.ui_module = None
30 self.server = None
31
32 def _run_command(self, command, path, default=None):
33 try:
34 result, _ = bb.process.run(command, cwd=path)
35 result = result.strip()
36 except bb.process.ExecutionError:
37 result = default
38 return result
39
40 def _handle_git_remote(self, remote):
41 if "://" not in remote:
42 if ':' in remote:
43 # This is assumed to be ssh
44 remote = "ssh://" + remote
45 else:
46 # This is assumed to be a file path
47 remote = "file://" + remote
48 return remote
49
50 def _get_bitbake_info(self):
51 """Return a tuple of bitbake information"""
52
53 # Our path SHOULD be .../bitbake/lib/layerindex/cooker.py
54 bb_path = os.path.dirname(__file__) # .../bitbake/lib/layerindex/cooker.py
55 bb_path = os.path.dirname(bb_path) # .../bitbake/lib/layerindex
56 bb_path = os.path.dirname(bb_path) # .../bitbake/lib
57 bb_path = os.path.dirname(bb_path) # .../bitbake
58 bb_path = self._run_command('git rev-parse --show-toplevel', os.path.dirname(__file__), default=bb_path)
59 bb_branch = self._run_command('git rev-parse --abbrev-ref HEAD', bb_path, default="<unknown>")
60 bb_rev = self._run_command('git rev-parse HEAD', bb_path, default="<unknown>")
61 for remotes in self._run_command('git remote -v', bb_path, default="").split("\n"):
62 remote = remotes.split("\t")[1].split(" ")[0]
63 if "(fetch)" == remotes.split("\t")[1].split(" ")[1]:
64 bb_remote = self._handle_git_remote(remote)
65 break
66 else:
67 bb_remote = self._handle_git_remote(bb_path)
68
69 return (bb_remote, bb_branch, bb_rev, bb_path)
70
71 def _load_bblayers(self, branches=None):
72 """Load the BBLAYERS and related collection information"""
73
74 d = self.layerindex.data
75
76 if not branches:
77 raise layerindexlib.LayerIndexFetchError("No branches specified for _load_bblayers!")
78
79 index = layerindexlib.LayerIndexObj()
80
81 branchId = 0
82 index.branches = {}
83
84 layerItemId = 0
85 index.layerItems = {}
86
87 layerBranchId = 0
88 index.layerBranches = {}
89
90 bblayers = d.getVar('BBLAYERS').split()
91
92 if not bblayers:
93 # It's blank! Nothing to process...
94 return index
95
96 collections = d.getVar('BBFILE_COLLECTIONS')
97 layerconfs = d.varhistory.get_variable_items_files('BBFILE_COLLECTIONS')
98 bbfile_collections = {layer: os.path.dirname(os.path.dirname(path)) for layer, path in layerconfs.items()}
99
100 (_, bb_branch, _, _) = self._get_bitbake_info()
101
102 for branch in branches:
103 branchId += 1
104 index.branches[branchId] = layerindexlib.Branch(index, None)
105 index.branches[branchId].define_data(branchId, branch, bb_branch)
106
107 for entry in collections.split():
108 layerpath = entry
109 if entry in bbfile_collections:
110 layerpath = bbfile_collections[entry]
111
112 layername = d.getVar('BBLAYERS_LAYERINDEX_NAME_%s' % entry) or os.path.basename(layerpath)
113 layerversion = d.getVar('LAYERVERSION_%s' % entry) or ""
114 layerurl = self._handle_git_remote(layerpath)
115
116 layersubdir = ""
117 layerrev = "<unknown>"
118 layerbranch = "<unknown>"
119
120 if os.path.isdir(layerpath):
121 layerbasepath = self._run_command('git rev-parse --show-toplevel', layerpath, default=layerpath)
122 if os.path.abspath(layerpath) != os.path.abspath(layerbasepath):
123 layersubdir = os.path.abspath(layerpath)[len(layerbasepath) + 1:]
124
125 layerbranch = self._run_command('git rev-parse --abbrev-ref HEAD', layerpath, default="<unknown>")
126 layerrev = self._run_command('git rev-parse HEAD', layerpath, default="<unknown>")
127
128 for remotes in self._run_command('git remote -v', layerpath, default="").split("\n"):
129 if not remotes:
130 layerurl = self._handle_git_remote(layerpath)
131 else:
132 remote = remotes.split("\t")[1].split(" ")[0]
133 if "(fetch)" == remotes.split("\t")[1].split(" ")[1]:
134 layerurl = self._handle_git_remote(remote)
135 break
136
137 layerItemId += 1
138 index.layerItems[layerItemId] = layerindexlib.LayerItem(index, None)
139 index.layerItems[layerItemId].define_data(layerItemId, layername, description=layerpath, vcs_url=layerurl)
140
141 for branchId in index.branches:
142 layerBranchId += 1
143 index.layerBranches[layerBranchId] = layerindexlib.LayerBranch(index, None)
144 index.layerBranches[layerBranchId].define_data(layerBranchId, entry, layerversion, layerItemId, branchId,
145 vcs_subdir=layersubdir, vcs_last_rev=layerrev, actual_branch=layerbranch)
146
147 return index
148
149
150 def load_index(self, url, load):
151 """
152 Fetches layer information from a build configuration.
153
154 The return value is a dictionary containing API,
155 layer, branch, dependency, recipe, machine, distro, information.
156
157 url type should be 'cooker'.
158 url path is ignored
159 """
160
161 up = urlparse(url)
162
163 if up.scheme != 'cooker':
164 raise layerindexlib.plugin.LayerIndexPluginUrlError(self.type, url)
165
166 d = self.layerindex.data
167
168 params = self.layerindex._parse_params(up.params)
169
170 # Only reason to pass a branch is to emulate them...
171 if 'branch' in params:
172 branches = params['branch'].split(',')
173 else:
174 branches = ['HEAD']
175
176 logger.debug("Loading cooker data branches %s" % branches)
177
178 index = self._load_bblayers(branches=branches)
179
180 index.config = {}
181 index.config['TYPE'] = self.type
182 index.config['URL'] = url
183
184 if 'desc' in params:
185 index.config['DESCRIPTION'] = unquote(params['desc'])
186 else:
187 index.config['DESCRIPTION'] = 'local'
188
189 if 'cache' in params:
190 index.config['CACHE'] = params['cache']
191
192 index.config['BRANCH'] = branches
193
194 # ("layerDependencies", layerindexlib.LayerDependency)
195 layerDependencyId = 0
196 if "layerDependencies" in load:
197 index.layerDependencies = {}
198 for layerBranchId in index.layerBranches:
199 branchName = index.layerBranches[layerBranchId].branch.name
200 collection = index.layerBranches[layerBranchId].collection
201
202 def add_dependency(layerDependencyId, index, deps, required):
203 try:
204 depDict = bb.utils.explode_dep_versions2(deps)
205 except bb.utils.VersionStringException as vse:
206 bb.fatal('Error parsing LAYERDEPENDS_%s: %s' % (collection, str(vse)))
207
208 for dep, oplist in list(depDict.items()):
209 # We need to search ourselves, so use the _ version...
210 depLayerBranch = index.find_collection(dep, branches=[branchName])
211 if not depLayerBranch:
212 # Missing dependency?!
213 logger.error('Missing dependency %s (%s)' % (dep, branchName))
214 continue
215
216 # We assume that the oplist matches...
217 layerDependencyId += 1
218 layerDependency = layerindexlib.LayerDependency(index, None)
219 layerDependency.define_data(id=layerDependencyId,
220 required=required, layerbranch=layerBranchId,
221 dependency=depLayerBranch.layer_id)
222
223 logger.debug('%s requires %s' % (layerDependency.layer.name, layerDependency.dependency.name))
224 index.add_element("layerDependencies", [layerDependency])
225
226 return layerDependencyId
227
228 deps = d.getVar("LAYERDEPENDS_%s" % collection)
229 if deps:
230 layerDependencyId = add_dependency(layerDependencyId, index, deps, True)
231
232 deps = d.getVar("LAYERRECOMMENDS_%s" % collection)
233 if deps:
234 layerDependencyId = add_dependency(layerDependencyId, index, deps, False)
235
236 # Need to load recipes here (requires cooker access)
237 recipeId = 0
238 ## TODO: NOT IMPLEMENTED
239 # The code following this is an example of what needs to be
240 # implemented. However, it does not work as-is.
241 if False and 'recipes' in load:
242 index.recipes = {}
243
244 ret = self.ui_module.main(self.server_connection.connection, self.server_connection.events, config_params)
245
246 all_versions = self._run_command('allProviders')
247
248 all_versions_list = defaultdict(list, all_versions)
249 for pn in all_versions_list:
250 for ((pe, pv, pr), fpath) in all_versions_list[pn]:
251 realfn = bb.cache.virtualfn2realfn(fpath)
252
253 filepath = os.path.dirname(realfn[0])
254 filename = os.path.basename(realfn[0])
255
256 # This is all HORRIBLY slow, and likely unnecessary
257 #dscon = self._run_command('parseRecipeFile', fpath, False, [])
258 #connector = myDataStoreConnector(self, dscon.dsindex)
259 #recipe_data = bb.data.init()
260 #recipe_data.setVar('_remote_data', connector)
261
262 #summary = recipe_data.getVar('SUMMARY')
263 #description = recipe_data.getVar('DESCRIPTION')
264 #section = recipe_data.getVar('SECTION')
265 #license = recipe_data.getVar('LICENSE')
266 #homepage = recipe_data.getVar('HOMEPAGE')
267 #bugtracker = recipe_data.getVar('BUGTRACKER')
268 #provides = recipe_data.getVar('PROVIDES')
269
270 layer = bb.utils.get_file_layer(realfn[0], self.config_data)
271
272 depBranchId = collection[layer]
273
274 recipeId += 1
275 recipe = layerindexlib.Recipe(index, None)
276 recipe.define_data(id=recipeId,
277 filename=filename, filepath=filepath,
278 pn=pn, pv=pv,
279 summary=pn, description=pn, section='?',
280 license='?', homepage='?', bugtracker='?',
281 provides='?', bbclassextend='?', inherits='?',
282 disallowed='?', layerbranch=depBranchId)
283
284 index = addElement("recipes", [recipe], index)
285
286 # ("machines", layerindexlib.Machine)
287 machineId = 0
288 if 'machines' in load:
289 index.machines = {}
290
291 for layerBranchId in index.layerBranches:
292 # load_bblayers uses the description to cache the actual path...
293 machine_path = index.layerBranches[layerBranchId].layer.description
294 machine_path = os.path.join(machine_path, 'conf/machine')
295 if os.path.isdir(machine_path):
296 for (dirpath, _, filenames) in os.walk(machine_path):
297 # Ignore subdirs...
298 if not dirpath.endswith('conf/machine'):
299 continue
300 for fname in filenames:
301 if fname.endswith('.conf'):
302 machineId += 1
303 machine = layerindexlib.Machine(index, None)
304 machine.define_data(id=machineId, name=fname[:-5],
305 description=fname[:-5],
306 layerbranch=index.layerBranches[layerBranchId])
307
308 index.add_element("machines", [machine])
309
310 # ("distros", layerindexlib.Distro)
311 distroId = 0
312 if 'distros' in load:
313 index.distros = {}
314
315 for layerBranchId in index.layerBranches:
316 # load_bblayers uses the description to cache the actual path...
317 distro_path = index.layerBranches[layerBranchId].layer.description
318 distro_path = os.path.join(distro_path, 'conf/distro')
319 if os.path.isdir(distro_path):
320 for (dirpath, _, filenames) in os.walk(distro_path):
321 # Ignore subdirs...
322 if not dirpath.endswith('conf/distro'):
323 continue
324 for fname in filenames:
325 if fname.endswith('.conf'):
326 distroId += 1
327 distro = layerindexlib.Distro(index, None)
328 distro.define_data(id=distroId, name=fname[:-5],
329 description=fname[:-5],
330 layerbranch=index.layerBranches[layerBranchId])
331
332 index.add_element("distros", [distro])
333
334 return index
diff --git a/bitbake/lib/layerindexlib/plugin.py b/bitbake/lib/layerindexlib/plugin.py
deleted file mode 100644
index cadda36c74..0000000000
--- a/bitbake/lib/layerindexlib/plugin.py
+++ /dev/null
@@ -1,46 +0,0 @@
1# Copyright (C) 2016-2018 Wind River Systems, Inc.
2#
3# SPDX-License-Identifier: GPL-2.0-only
4#
5# The file contains:
6# LayerIndex exceptions
7# Plugin base class
8# Utility Functions for working on layerindex data
9
10import logging
11
12logger = logging.getLogger('BitBake.layerindexlib.plugin')
13
14class LayerIndexPluginException(Exception):
15 """LayerIndex Generic Exception"""
16 def __init__(self, message):
17 self.msg = message
18 Exception.__init__(self, message)
19
20 def __str__(self):
21 return self.msg
22
23class LayerIndexPluginUrlError(LayerIndexPluginException):
24 """Exception raised when a plugin does not support a given URL type"""
25 def __init__(self, plugin, url):
26 msg = "%s does not support %s:" % (plugin, url)
27 self.plugin = plugin
28 self.url = url
29 LayerIndexPluginException.__init__(self, msg)
30
31class IndexPlugin():
32 def __init__(self):
33 self.type = None
34
35 def init(self, layerindex):
36 self.layerindex = layerindex
37
38 def plugin_type(self):
39 return self.type
40
41 def load_index(self, uri):
42 raise NotImplementedError('load_index is not implemented')
43
44 def store_index(self, uri, index):
45 raise NotImplementedError('store_index is not implemented')
46
diff --git a/bitbake/lib/layerindexlib/restapi.py b/bitbake/lib/layerindexlib/restapi.py
deleted file mode 100644
index 81d99b02ea..0000000000
--- a/bitbake/lib/layerindexlib/restapi.py
+++ /dev/null
@@ -1,392 +0,0 @@
1# Copyright (C) 2016-2018 Wind River Systems, Inc.
2#
3# SPDX-License-Identifier: GPL-2.0-only
4#
5
6import logging
7import json
8import os
9
10from urllib.parse import unquote
11from urllib.parse import urlparse
12
13import bb
14
15import layerindexlib
16import layerindexlib.plugin
17
18logger = logging.getLogger('BitBake.layerindexlib.restapi')
19
20def plugin_init(plugins):
21 return RestApiPlugin()
22
23class RestApiPlugin(layerindexlib.plugin.IndexPlugin):
24 def __init__(self):
25 self.type = "restapi"
26
27 def load_index(self, url, load):
28 """
29 Fetches layer information from a local or remote layer index.
30
31 The return value is a LayerIndexObj.
32
33 url is the url to the rest api of the layer index, such as:
34 https://layers.openembedded.org/layerindex/api/
35
36 Or a local file...
37 """
38
39 up = urlparse(url)
40
41 if up.scheme == 'file':
42 return self.load_index_file(up, url, load)
43
44 if up.scheme == 'http' or up.scheme == 'https':
45 return self.load_index_web(up, url, load)
46
47 raise layerindexlib.plugin.LayerIndexPluginUrlError(self.type, url)
48
49
50 def load_index_file(self, up, url, load):
51 """
52 Fetches layer information from a local file or directory.
53
54 The return value is a LayerIndexObj.
55
56 ud is the parsed url to the local file or directory.
57 """
58 if not os.path.exists(up.path):
59 raise FileNotFoundError(up.path)
60
61 index = layerindexlib.LayerIndexObj()
62
63 index.config = {}
64 index.config['TYPE'] = self.type
65 index.config['URL'] = url
66
67 params = self.layerindex._parse_params(up.params)
68
69 if 'desc' in params:
70 index.config['DESCRIPTION'] = unquote(params['desc'])
71 else:
72 index.config['DESCRIPTION'] = up.path
73
74 if 'cache' in params:
75 index.config['CACHE'] = params['cache']
76
77 if 'branch' in params:
78 branches = params['branch'].split(',')
79 index.config['BRANCH'] = branches
80 else:
81 branches = ['*']
82
83
84 def load_cache(path, index, branches=[]):
85 logger.debug('Loading json file %s' % path)
86 with open(path, 'rt', encoding='utf-8') as f:
87 pindex = json.load(f)
88
89 # Filter the branches on loaded files...
90 newpBranch = []
91 for branch in branches:
92 if branch != '*':
93 if 'branches' in pindex:
94 for br in pindex['branches']:
95 if br['name'] == branch:
96 newpBranch.append(br)
97 else:
98 if 'branches' in pindex:
99 for br in pindex['branches']:
100 newpBranch.append(br)
101
102 if newpBranch:
103 index.add_raw_element('branches', layerindexlib.Branch, newpBranch)
104 else:
105 logger.debug('No matching branches (%s) in index file(s)' % branches)
106 # No matching branches.. return nothing...
107 return
108
109 for (lName, lType) in [("layerItems", layerindexlib.LayerItem),
110 ("layerBranches", layerindexlib.LayerBranch),
111 ("layerDependencies", layerindexlib.LayerDependency),
112 ("recipes", layerindexlib.Recipe),
113 ("machines", layerindexlib.Machine),
114 ("distros", layerindexlib.Distro)]:
115 if lName in pindex:
116 index.add_raw_element(lName, lType, pindex[lName])
117
118
119 if not os.path.isdir(up.path):
120 load_cache(up.path, index, branches)
121 return index
122
123 logger.debug('Loading from dir %s...' % (up.path))
124 for (dirpath, _, filenames) in os.walk(up.path):
125 for filename in filenames:
126 if not filename.endswith('.json'):
127 continue
128 fpath = os.path.join(dirpath, filename)
129 load_cache(fpath, index, branches)
130
131 return index
132
133
134 def load_index_web(self, up, url, load):
135 """
136 Fetches layer information from a remote layer index.
137
138 The return value is a LayerIndexObj.
139
140 ud is the parsed url to the rest api of the layer index, such as:
141 https://layers.openembedded.org/layerindex/api/
142 """
143
144 def _get_json_response(apiurl=None, username=None, password=None, retry=True):
145 assert apiurl is not None
146
147 logger.debug("fetching %s" % apiurl)
148
149 up = urlparse(apiurl)
150
151 username=up.username
152 password=up.password
153
154 # Strip username/password and params
155 if up.port:
156 up_stripped = up._replace(params="", netloc="%s:%s" % (up.hostname, up.port))
157 else:
158 up_stripped = up._replace(params="", netloc=up.hostname)
159
160 res = self.layerindex._fetch_url(up_stripped.geturl(), username=username, password=password)
161
162 try:
163 parsed = json.loads(res.read().decode('utf-8'))
164 except ConnectionResetError:
165 if retry:
166 logger.debug("%s: Connection reset by peer. Retrying..." % url)
167 parsed = _get_json_response(apiurl=up_stripped.geturl(), username=username, password=password, retry=False)
168 logger.debug("%s: retry successful.")
169 else:
170 raise layerindexlib.LayerIndexFetchError('%s: Connection reset by peer. Is there a firewall blocking your connection?' % apiurl)
171
172 return parsed
173
174 index = layerindexlib.LayerIndexObj()
175
176 index.config = {}
177 index.config['TYPE'] = self.type
178 index.config['URL'] = url
179
180 params = self.layerindex._parse_params(up.params)
181
182 if 'desc' in params:
183 index.config['DESCRIPTION'] = unquote(params['desc'])
184 else:
185 index.config['DESCRIPTION'] = up.hostname
186
187 if 'cache' in params:
188 index.config['CACHE'] = params['cache']
189
190 if 'branch' in params:
191 branches = params['branch'].split(',')
192 index.config['BRANCH'] = branches
193 else:
194 branches = ['*']
195
196 try:
197 index.apilinks = _get_json_response(apiurl=url, username=up.username, password=up.password)
198 except Exception as e:
199 raise layerindexlib.LayerIndexFetchError(url, e)
200
201 # Local raw index set...
202 pindex = {}
203
204 # Load all the requested branches at the same time time,
205 # a special branch of '*' means load all branches
206 filter = ""
207 if "*" not in branches:
208 filter = "?filter=name:%s" % "OR".join(branches)
209
210 logger.debug("Loading %s from %s" % (branches, index.apilinks['branches']))
211
212 # The link won't include username/password, so pull it from the original url
213 pindex['branches'] = _get_json_response(index.apilinks['branches'] + filter,
214 username=up.username, password=up.password)
215 if not pindex['branches']:
216 logger.debug("No valid branches (%s) found at url %s." % (branch, url))
217 return index
218 index.add_raw_element("branches", layerindexlib.Branch, pindex['branches'])
219
220 # Load all of the layerItems (these can not be easily filtered)
221 logger.debug("Loading %s from %s" % ('layerItems', index.apilinks['layerItems']))
222
223
224 # The link won't include username/password, so pull it from the original url
225 pindex['layerItems'] = _get_json_response(index.apilinks['layerItems'],
226 username=up.username, password=up.password)
227 if not pindex['layerItems']:
228 logger.debug("No layers were found at url %s." % (url))
229 return index
230 index.add_raw_element("layerItems", layerindexlib.LayerItem, pindex['layerItems'])
231
232
233 # From this point on load the contents for each branch. Otherwise we
234 # could run into a timeout.
235 for branch in index.branches:
236 filter = "?filter=branch__name:%s" % index.branches[branch].name
237
238 logger.debug("Loading %s from %s" % ('layerBranches', index.apilinks['layerBranches']))
239
240 # The link won't include username/password, so pull it from the original url
241 pindex['layerBranches'] = _get_json_response(index.apilinks['layerBranches'] + filter,
242 username=up.username, password=up.password)
243 if not pindex['layerBranches']:
244 logger.debug("No valid layer branches (%s) found at url %s." % (branches or "*", url))
245 return index
246 index.add_raw_element("layerBranches", layerindexlib.LayerBranch, pindex['layerBranches'])
247
248
249 # Load the rest, they all have a similar format
250 # Note: the layer index has a few more items, we can add them if necessary
251 # in the future.
252 filter = "?filter=layerbranch__branch__name:%s" % index.branches[branch].name
253 for (lName, lType) in [("layerDependencies", layerindexlib.LayerDependency),
254 ("recipes", layerindexlib.Recipe),
255 ("machines", layerindexlib.Machine),
256 ("distros", layerindexlib.Distro)]:
257 if lName not in load:
258 continue
259 logger.debug("Loading %s from %s" % (lName, index.apilinks[lName]))
260
261 # The link won't include username/password, so pull it from the original url
262 pindex[lName] = _get_json_response(index.apilinks[lName] + filter,
263 username=up.username, password=up.password)
264 index.add_raw_element(lName, lType, pindex[lName])
265
266 return index
267
268 def store_index(self, url, index):
269 """
270 Store layer information into a local file/dir.
271
272 The return value is a dictionary containing API,
273 layer, branch, dependency, recipe, machine, distro, information.
274
275 ud is a parsed url to a directory or file. If the path is a
276 directory, we will split the files into one file per layer.
277 If the path is to a file (exists or not) the entire DB will be
278 dumped into that one file.
279 """
280
281 up = urlparse(url)
282
283 if up.scheme != 'file':
284 raise layerindexlib.plugin.LayerIndexPluginUrlError(self.type, url)
285
286 logger.debug("Storing to %s..." % up.path)
287
288 try:
289 layerbranches = index.layerBranches
290 except KeyError:
291 logger.error('No layerBranches to write.')
292 return
293
294
295 def filter_item(layerbranchid, objects):
296 filtered = []
297 for obj in getattr(index, objects, None):
298 try:
299 if getattr(index, objects)[obj].layerbranch_id == layerbranchid:
300 filtered.append(getattr(index, objects)[obj]._data)
301 except AttributeError:
302 logger.debug('No obj.layerbranch_id: %s' % objects)
303 # No simple filter method, just include it...
304 try:
305 filtered.append(getattr(index, objects)[obj]._data)
306 except AttributeError:
307 logger.debug('No obj._data: %s %s' % (objects, type(obj)))
308 filtered.append(obj)
309 return filtered
310
311
312 # Write out to a single file.
313 # Filter out unnecessary items, then sort as we write for determinism
314 if not os.path.isdir(up.path):
315 pindex = {}
316
317 pindex['branches'] = []
318 pindex['layerItems'] = []
319 pindex['layerBranches'] = []
320
321 for layerbranchid in layerbranches:
322 if layerbranches[layerbranchid].branch._data not in pindex['branches']:
323 pindex['branches'].append(layerbranches[layerbranchid].branch._data)
324
325 if layerbranches[layerbranchid].layer._data not in pindex['layerItems']:
326 pindex['layerItems'].append(layerbranches[layerbranchid].layer._data)
327
328 if layerbranches[layerbranchid]._data not in pindex['layerBranches']:
329 pindex['layerBranches'].append(layerbranches[layerbranchid]._data)
330
331 for entry in index._index:
332 # Skip local items, apilinks and items already processed
333 if entry in index.config['local'] or \
334 entry == 'apilinks' or \
335 entry == 'branches' or \
336 entry == 'layerBranches' or \
337 entry == 'layerItems':
338 continue
339 if entry not in pindex:
340 pindex[entry] = []
341 pindex[entry].extend(filter_item(layerbranchid, entry))
342
343 bb.debug(1, 'Writing index to %s' % up.path)
344 with open(up.path, 'wt') as f:
345 json.dump(layerindexlib.sort_entry(pindex), f, indent=4)
346 return
347
348
349 # Write out to a directory one file per layerBranch
350 # Prepare all layer related items, to create a minimal file.
351 # We have to sort the entries as we write so they are deterministic
352 for layerbranchid in layerbranches:
353 pindex = {}
354
355 for entry in index._index:
356 # Skip local items, apilinks and items already processed
357 if entry in index.config['local'] or \
358 entry == 'apilinks' or \
359 entry == 'branches' or \
360 entry == 'layerBranches' or \
361 entry == 'layerItems':
362 continue
363 pindex[entry] = filter_item(layerbranchid, entry)
364
365 # Add the layer we're processing as the first one...
366 pindex['branches'] = [layerbranches[layerbranchid].branch._data]
367 pindex['layerItems'] = [layerbranches[layerbranchid].layer._data]
368 pindex['layerBranches'] = [layerbranches[layerbranchid]._data]
369
370 # We also need to include the layerbranch for any dependencies...
371 for layerdep in pindex['layerDependencies']:
372 layerdependency = layerindexlib.LayerDependency(index, layerdep)
373
374 layeritem = layerdependency.dependency
375 layerbranch = layerdependency.dependency_layerBranch
376
377 # We need to avoid duplicates...
378 if layeritem._data not in pindex['layerItems']:
379 pindex['layerItems'].append(layeritem._data)
380
381 if layerbranch._data not in pindex['layerBranches']:
382 pindex['layerBranches'].append(layerbranch._data)
383
384 # apply mirroring adjustments here....
385
386 fname = index.config['DESCRIPTION'] + '__' + pindex['branches'][0]['name'] + '__' + pindex['layerItems'][0]['name']
387 fname = fname.translate(str.maketrans('/ ', '__'))
388 fpath = os.path.join(up.path, fname)
389
390 bb.debug(1, 'Writing index to %s' % fpath + '.json')
391 with open(fpath + '.json', 'wt') as f:
392 json.dump(layerindexlib.sort_entry(pindex), f, indent=4)
diff --git a/bitbake/lib/layerindexlib/tests/__init__.py b/bitbake/lib/layerindexlib/tests/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
--- a/bitbake/lib/layerindexlib/tests/__init__.py
+++ /dev/null
diff --git a/bitbake/lib/layerindexlib/tests/common.py b/bitbake/lib/layerindexlib/tests/common.py
deleted file mode 100644
index 077382f11d..0000000000
--- a/bitbake/lib/layerindexlib/tests/common.py
+++ /dev/null
@@ -1,33 +0,0 @@
1# Copyright (C) 2017-2018 Wind River Systems, Inc.
2#
3# SPDX-License-Identifier: GPL-2.0-only
4#
5
6import unittest
7import tempfile
8import os
9import bb
10
11import logging
12
13class LayersTest(unittest.TestCase):
14
15 def setUp(self):
16 self.origdir = os.getcwd()
17 self.d = bb.data.init()
18 # At least one variable needs to be set
19 self.d.setVar('DL_DIR', os.getcwd())
20
21 if os.environ.get("BB_SKIP_NETTESTS") == "yes":
22 self.d.setVar('BB_NO_NETWORK', '1')
23
24 self.tempdir = tempfile.mkdtemp()
25 self.logger = logging.getLogger("BitBake")
26
27 def tearDown(self):
28 os.chdir(self.origdir)
29 if os.environ.get("BB_TMPDIR_NOCLEAN") == "yes":
30 print("Not cleaning up %s. Please remove manually." % self.tempdir)
31 else:
32 bb.utils.prunedir(self.tempdir)
33
diff --git a/bitbake/lib/layerindexlib/tests/cooker.py b/bitbake/lib/layerindexlib/tests/cooker.py
deleted file mode 100644
index 5ddf89aa21..0000000000
--- a/bitbake/lib/layerindexlib/tests/cooker.py
+++ /dev/null
@@ -1,110 +0,0 @@
1# Copyright (C) 2018 Wind River Systems, Inc.
2#
3# SPDX-License-Identifier: GPL-2.0-only
4#
5
6import os
7import bb
8
9import layerindexlib
10from layerindexlib.tests.common import LayersTest
11
12
13class LayerIndexCookerTest(LayersTest):
14
15 def setUp(self):
16 LayersTest.setUp(self)
17
18 # Note this is NOT a comprehensive test of cooker, as we can't easily
19 # configure the test data. But we can emulate the basics of the layer.conf
20 # files, so that is what we will do.
21
22 new_topdir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "testdata")
23 new_bbpath = os.path.join(new_topdir, "build")
24
25 self.d.setVar('TOPDIR', new_topdir)
26 self.d.setVar('BBPATH', new_bbpath)
27
28 self.d = bb.parse.handle("%s/conf/bblayers.conf" % new_bbpath, self.d, True)
29 for layer in self.d.getVar('BBLAYERS').split():
30 self.d = bb.parse.handle("%s/conf/layer.conf" % layer, self.d, True)
31
32 self.layerindex = layerindexlib.LayerIndex(self.d)
33 self.layerindex.load_layerindex('cooker://', load=['layerDependencies'])
34
35 def test_layerindex_is_empty(self):
36 self.assertFalse(self.layerindex.is_empty(), msg="Layerindex is not empty!")
37
38 def test_dependency_resolution(self):
39 # Verify depth first searching...
40 (dependencies, invalidnames) = self.layerindex.find_dependencies(names=['meta-python'])
41
42 first = True
43 for deplayerbranch in dependencies:
44 layerBranch = dependencies[deplayerbranch][0]
45 layerDeps = dependencies[deplayerbranch][1:]
46
47 if not first:
48 continue
49
50 first = False
51
52 # Top of the deps should be openembedded-core, since everything depends on it.
53 self.assertEqual(layerBranch.layer.name, "openembedded-core", msg='Top dependency not openembedded-core')
54
55 # meta-python should cause an openembedded-core dependency, if not assert!
56 for dep in layerDeps:
57 if dep.layer.name == 'meta-python':
58 break
59 else:
60 self.assertTrue(False, msg='meta-python was not found')
61
62 # Only check the first element...
63 break
64 else:
65 if first:
66 # Empty list, this is bad.
67 self.assertTrue(False, msg='Empty list of dependencies')
68
69 # Last dep should be the requested item
70 layerBranch = dependencies[deplayerbranch][0]
71 self.assertEqual(layerBranch.layer.name, "meta-python", msg='Last dependency not meta-python')
72
73 def test_find_collection(self):
74 def _check(collection, expected):
75 self.logger.debug("Looking for collection %s..." % collection)
76 result = self.layerindex.find_collection(collection)
77 if expected:
78 self.assertIsNotNone(result, msg="Did not find %s when it shouldn't be there" % collection)
79 else:
80 self.assertIsNone(result, msg="Found %s when it should be there" % collection)
81
82 tests = [ ('core', True),
83 ('openembedded-core', False),
84 ('networking-layer', True),
85 ('meta-python', True),
86 ('openembedded-layer', True),
87 ('notpresent', False) ]
88
89 for collection,result in tests:
90 _check(collection, result)
91
92 def test_find_layerbranch(self):
93 def _check(name, expected):
94 self.logger.debug("Looking for layerbranch %s..." % name)
95 result = self.layerindex.find_layerbranch(name)
96 if expected:
97 self.assertIsNotNone(result, msg="Did not find %s when it shouldn't be there" % collection)
98 else:
99 self.assertIsNone(result, msg="Found %s when it should be there" % collection)
100
101 tests = [ ('openembedded-core', True),
102 ('core', False),
103 ('networking-layer', True),
104 ('meta-python', True),
105 ('openembedded-layer', True),
106 ('notpresent', False) ]
107
108 for collection,result in tests:
109 _check(collection, result)
110
diff --git a/bitbake/lib/layerindexlib/tests/layerindexobj.py b/bitbake/lib/layerindexlib/tests/layerindexobj.py
deleted file mode 100644
index de1e474687..0000000000
--- a/bitbake/lib/layerindexlib/tests/layerindexobj.py
+++ /dev/null
@@ -1,210 +0,0 @@
1# Copyright (C) 2017-2018 Wind River Systems, Inc.
2#
3# SPDX-License-Identifier: GPL-2.0-only
4#
5
6from layerindexlib.tests.common import LayersTest
7
8
9class LayerIndexObjectsTest(LayersTest):
10 def setUp(self):
11 from layerindexlib import LayerIndexObj, Branch, LayerItem, LayerBranch, LayerDependency, Recipe, Machine, Distro
12
13 LayersTest.setUp(self)
14
15 self.index = LayerIndexObj()
16
17 branchId = 0
18 layerItemId = 0
19 layerBranchId = 0
20 layerDependencyId = 0
21 recipeId = 0
22 machineId = 0
23 distroId = 0
24
25 self.index.branches = {}
26 self.index.layerItems = {}
27 self.index.layerBranches = {}
28 self.index.layerDependencies = {}
29 self.index.recipes = {}
30 self.index.machines = {}
31 self.index.distros = {}
32
33 branchId += 1
34 self.index.branches[branchId] = Branch(self.index)
35 self.index.branches[branchId].define_data(branchId,
36 'test_branch', 'bb_test_branch')
37 self.index.branches[branchId].lockData()
38
39 layerItemId +=1
40 self.index.layerItems[layerItemId] = LayerItem(self.index)
41 self.index.layerItems[layerItemId].define_data(layerItemId,
42 'test_layerItem', vcs_url='git://git_test_url/test_layerItem')
43 self.index.layerItems[layerItemId].lockData()
44
45 layerBranchId +=1
46 self.index.layerBranches[layerBranchId] = LayerBranch(self.index)
47 self.index.layerBranches[layerBranchId].define_data(layerBranchId,
48 'test_collection', '99', layerItemId,
49 branchId)
50
51 recipeId += 1
52 self.index.recipes[recipeId] = Recipe(self.index)
53 self.index.recipes[recipeId].define_data(recipeId, 'test_git.bb',
54 'recipes-test', 'test', 'git',
55 layerBranchId)
56
57 machineId += 1
58 self.index.machines[machineId] = Machine(self.index)
59 self.index.machines[machineId].define_data(machineId,
60 'test_machine', 'test_machine',
61 layerBranchId)
62
63 distroId += 1
64 self.index.distros[distroId] = Distro(self.index)
65 self.index.distros[distroId].define_data(distroId,
66 'test_distro', 'test_distro',
67 layerBranchId)
68
69 layerItemId +=1
70 self.index.layerItems[layerItemId] = LayerItem(self.index)
71 self.index.layerItems[layerItemId].define_data(layerItemId, 'test_layerItem 2',
72 vcs_url='git://git_test_url/test_layerItem')
73
74 layerBranchId +=1
75 self.index.layerBranches[layerBranchId] = LayerBranch(self.index)
76 self.index.layerBranches[layerBranchId].define_data(layerBranchId,
77 'test_collection_2', '72', layerItemId,
78 branchId, actual_branch='some_other_branch')
79
80 layerDependencyId += 1
81 self.index.layerDependencies[layerDependencyId] = LayerDependency(self.index)
82 self.index.layerDependencies[layerDependencyId].define_data(layerDependencyId,
83 layerBranchId, 1)
84
85 layerDependencyId += 1
86 self.index.layerDependencies[layerDependencyId] = LayerDependency(self.index)
87 self.index.layerDependencies[layerDependencyId].define_data(layerDependencyId,
88 layerBranchId, 1, required=False)
89
90 def test_branch(self):
91 branch = self.index.branches[1]
92 self.assertEqual(branch.id, 1)
93 self.assertEqual(branch.name, 'test_branch')
94 self.assertEqual(branch.short_description, 'test_branch')
95 self.assertEqual(branch.bitbake_branch, 'bb_test_branch')
96
97 def test_layerItem(self):
98 layerItem = self.index.layerItems[1]
99 self.assertEqual(layerItem.id, 1)
100 self.assertEqual(layerItem.name, 'test_layerItem')
101 self.assertEqual(layerItem.summary, 'test_layerItem')
102 self.assertEqual(layerItem.description, 'test_layerItem')
103 self.assertEqual(layerItem.vcs_url, 'git://git_test_url/test_layerItem')
104 self.assertEqual(layerItem.vcs_web_url, None)
105 self.assertIsNone(layerItem.vcs_web_tree_base_url)
106 self.assertIsNone(layerItem.vcs_web_file_base_url)
107 self.assertIsNotNone(layerItem.updated)
108
109 layerItem = self.index.layerItems[2]
110 self.assertEqual(layerItem.id, 2)
111 self.assertEqual(layerItem.name, 'test_layerItem 2')
112 self.assertEqual(layerItem.summary, 'test_layerItem 2')
113 self.assertEqual(layerItem.description, 'test_layerItem 2')
114 self.assertEqual(layerItem.vcs_url, 'git://git_test_url/test_layerItem')
115 self.assertIsNone(layerItem.vcs_web_url)
116 self.assertIsNone(layerItem.vcs_web_tree_base_url)
117 self.assertIsNone(layerItem.vcs_web_file_base_url)
118 self.assertIsNotNone(layerItem.updated)
119
120 def test_layerBranch(self):
121 layerBranch = self.index.layerBranches[1]
122 self.assertEqual(layerBranch.id, 1)
123 self.assertEqual(layerBranch.collection, 'test_collection')
124 self.assertEqual(layerBranch.version, '99')
125 self.assertEqual(layerBranch.vcs_subdir, '')
126 self.assertEqual(layerBranch.actual_branch, 'test_branch')
127 self.assertIsNotNone(layerBranch.updated)
128 self.assertEqual(layerBranch.layer_id, 1)
129 self.assertEqual(layerBranch.branch_id, 1)
130 self.assertEqual(layerBranch.layer, self.index.layerItems[1])
131 self.assertEqual(layerBranch.branch, self.index.branches[1])
132
133 layerBranch = self.index.layerBranches[2]
134 self.assertEqual(layerBranch.id, 2)
135 self.assertEqual(layerBranch.collection, 'test_collection_2')
136 self.assertEqual(layerBranch.version, '72')
137 self.assertEqual(layerBranch.vcs_subdir, '')
138 self.assertEqual(layerBranch.actual_branch, 'some_other_branch')
139 self.assertIsNotNone(layerBranch.updated)
140 self.assertEqual(layerBranch.layer_id, 2)
141 self.assertEqual(layerBranch.branch_id, 1)
142 self.assertEqual(layerBranch.layer, self.index.layerItems[2])
143 self.assertEqual(layerBranch.branch, self.index.branches[1])
144
145 def test_layerDependency(self):
146 layerDependency = self.index.layerDependencies[1]
147 self.assertEqual(layerDependency.id, 1)
148 self.assertEqual(layerDependency.layerbranch_id, 2)
149 self.assertEqual(layerDependency.layerbranch, self.index.layerBranches[2])
150 self.assertEqual(layerDependency.layer_id, 2)
151 self.assertEqual(layerDependency.layer, self.index.layerItems[2])
152 self.assertTrue(layerDependency.required)
153 self.assertEqual(layerDependency.dependency_id, 1)
154 self.assertEqual(layerDependency.dependency, self.index.layerItems[1])
155 self.assertEqual(layerDependency.dependency_layerBranch, self.index.layerBranches[1])
156
157 layerDependency = self.index.layerDependencies[2]
158 self.assertEqual(layerDependency.id, 2)
159 self.assertEqual(layerDependency.layerbranch_id, 2)
160 self.assertEqual(layerDependency.layerbranch, self.index.layerBranches[2])
161 self.assertEqual(layerDependency.layer_id, 2)
162 self.assertEqual(layerDependency.layer, self.index.layerItems[2])
163 self.assertFalse(layerDependency.required)
164 self.assertEqual(layerDependency.dependency_id, 1)
165 self.assertEqual(layerDependency.dependency, self.index.layerItems[1])
166 self.assertEqual(layerDependency.dependency_layerBranch, self.index.layerBranches[1])
167
168 def test_recipe(self):
169 recipe = self.index.recipes[1]
170 self.assertEqual(recipe.id, 1)
171 self.assertEqual(recipe.layerbranch_id, 1)
172 self.assertEqual(recipe.layerbranch, self.index.layerBranches[1])
173 self.assertEqual(recipe.layer_id, 1)
174 self.assertEqual(recipe.layer, self.index.layerItems[1])
175 self.assertEqual(recipe.filename, 'test_git.bb')
176 self.assertEqual(recipe.filepath, 'recipes-test')
177 self.assertEqual(recipe.fullpath, 'recipes-test/test_git.bb')
178 self.assertEqual(recipe.summary, "")
179 self.assertEqual(recipe.description, "")
180 self.assertEqual(recipe.section, "")
181 self.assertEqual(recipe.pn, 'test')
182 self.assertEqual(recipe.pv, 'git')
183 self.assertEqual(recipe.license, "")
184 self.assertEqual(recipe.homepage, "")
185 self.assertEqual(recipe.bugtracker, "")
186 self.assertEqual(recipe.provides, "")
187 self.assertIsNotNone(recipe.updated)
188 self.assertEqual(recipe.inherits, "")
189
190 def test_machine(self):
191 machine = self.index.machines[1]
192 self.assertEqual(machine.id, 1)
193 self.assertEqual(machine.layerbranch_id, 1)
194 self.assertEqual(machine.layerbranch, self.index.layerBranches[1])
195 self.assertEqual(machine.layer_id, 1)
196 self.assertEqual(machine.layer, self.index.layerItems[1])
197 self.assertEqual(machine.name, 'test_machine')
198 self.assertEqual(machine.description, 'test_machine')
199 self.assertIsNotNone(machine.updated)
200
201 def test_distro(self):
202 distro = self.index.distros[1]
203 self.assertEqual(distro.id, 1)
204 self.assertEqual(distro.layerbranch_id, 1)
205 self.assertEqual(distro.layerbranch, self.index.layerBranches[1])
206 self.assertEqual(distro.layer_id, 1)
207 self.assertEqual(distro.layer, self.index.layerItems[1])
208 self.assertEqual(distro.name, 'test_distro')
209 self.assertEqual(distro.description, 'test_distro')
210 self.assertIsNotNone(distro.updated)
diff --git a/bitbake/lib/layerindexlib/tests/restapi.py b/bitbake/lib/layerindexlib/tests/restapi.py
deleted file mode 100644
index 71f0ae8a9d..0000000000
--- a/bitbake/lib/layerindexlib/tests/restapi.py
+++ /dev/null
@@ -1,171 +0,0 @@
1# Copyright (C) 2017-2018 Wind River Systems, Inc.
2#
3# SPDX-License-Identifier: GPL-2.0-only
4#
5
6import unittest
7import os
8
9import layerindexlib
10from layerindexlib.tests.common import LayersTest
11
12
13def skipIfNoNetwork():
14 if os.environ.get("BB_SKIP_NETTESTS") == "yes":
15 return unittest.skip("Network tests being skipped")
16 return lambda f: f
17
18class LayerIndexWebRestApiTest(LayersTest):
19
20 @skipIfNoNetwork()
21 def setUp(self):
22 self.assertFalse(os.environ.get("BB_SKIP_NETTESTS") == "yes", msg="BB_SKIP_NETTESTS set, but we tried to test anyway")
23 LayersTest.setUp(self)
24 self.layerindex = layerindexlib.LayerIndex(self.d)
25 self.layerindex.load_layerindex('https://layers.openembedded.org/layerindex/api/;branch=sumo', load=['layerDependencies'])
26
27 @skipIfNoNetwork()
28 def test_layerindex_is_empty(self):
29 self.assertFalse(self.layerindex.is_empty(), msg="Layerindex is empty")
30
31 @skipIfNoNetwork()
32 def test_layerindex_store_file(self):
33 self.layerindex.store_layerindex('file://%s/file.json' % self.tempdir, self.layerindex.indexes[0])
34
35 self.assertTrue(os.path.isfile('%s/file.json' % self.tempdir), msg="Temporary file was not created by store_layerindex")
36
37 reload = layerindexlib.LayerIndex(self.d)
38 reload.load_layerindex('file://%s/file.json' % self.tempdir)
39
40 self.assertFalse(reload.is_empty(), msg="Layerindex is empty")
41
42 # Calculate layerItems in original index that should NOT be in reload
43 layerItemNames = []
44 for itemId in self.layerindex.indexes[0].layerItems:
45 layerItemNames.append(self.layerindex.indexes[0].layerItems[itemId].name)
46
47 for layerBranchId in self.layerindex.indexes[0].layerBranches:
48 layerItemNames.remove(self.layerindex.indexes[0].layerBranches[layerBranchId].layer.name)
49
50 for itemId in reload.indexes[0].layerItems:
51 self.assertFalse(reload.indexes[0].layerItems[itemId].name in layerItemNames, msg="Item reloaded when it shouldn't have been")
52
53 # Compare the original to what we wrote...
54 for type in self.layerindex.indexes[0]._index:
55 if type == 'apilinks' or \
56 type == 'layerItems' or \
57 type in self.layerindex.indexes[0].config['local']:
58 continue
59 for id in getattr(self.layerindex.indexes[0], type):
60 self.logger.debug("type %s" % (type))
61
62 self.assertTrue(id in getattr(reload.indexes[0], type), msg="Id number not in reloaded index")
63
64 self.logger.debug("%s ? %s" % (getattr(self.layerindex.indexes[0], type)[id], getattr(reload.indexes[0], type)[id]))
65
66 self.assertEqual(getattr(self.layerindex.indexes[0], type)[id], getattr(reload.indexes[0], type)[id], msg="Reloaded contents different")
67
68 @skipIfNoNetwork()
69 def test_layerindex_store_split(self):
70 self.layerindex.store_layerindex('file://%s' % self.tempdir, self.layerindex.indexes[0])
71
72 reload = layerindexlib.LayerIndex(self.d)
73 reload.load_layerindex('file://%s' % self.tempdir)
74
75 self.assertFalse(reload.is_empty(), msg="Layer index is empty")
76
77 for type in self.layerindex.indexes[0]._index:
78 if type == 'apilinks' or \
79 type == 'layerItems' or \
80 type in self.layerindex.indexes[0].config['local']:
81 continue
82 for id in getattr(self.layerindex.indexes[0] ,type):
83 self.logger.debug("type %s" % (type))
84
85 self.assertTrue(id in getattr(reload.indexes[0], type), msg="Id number missing from reloaded data")
86
87 self.logger.debug("%s ? %s" % (getattr(self.layerindex.indexes[0] ,type)[id], getattr(reload.indexes[0], type)[id]))
88
89 self.assertEqual(getattr(self.layerindex.indexes[0] ,type)[id], getattr(reload.indexes[0], type)[id], msg="reloaded data does not match original")
90
91 @skipIfNoNetwork()
92 def test_dependency_resolution(self):
93 # Verify depth first searching...
94 (dependencies, invalidnames) = self.layerindex.find_dependencies(names=['meta-python'])
95
96 first = True
97 for deplayerbranch in dependencies:
98 layerBranch = dependencies[deplayerbranch][0]
99 layerDeps = dependencies[deplayerbranch][1:]
100
101 if not first:
102 continue
103
104 first = False
105
106 # Top of the deps should be openembedded-core, since everything depends on it.
107 self.assertEqual(layerBranch.layer.name, "openembedded-core", msg='OpenEmbedded-Core is no the first dependency')
108
109 # meta-python should cause an openembedded-core dependency, if not assert!
110 for dep in layerDeps:
111 if dep.layer.name == 'meta-python':
112 break
113 else:
114 self.logger.debug("meta-python was not found")
115 raise self.failureException
116
117 # Only check the first element...
118 break
119 else:
120 # Empty list, this is bad.
121 self.logger.debug("Empty list of dependencies")
122 self.assertIsNotNone(first, msg="Empty list of dependencies")
123
124 # Last dep should be the requested item
125 layerBranch = dependencies[deplayerbranch][0]
126 self.assertEqual(layerBranch.layer.name, "meta-python", msg="Last dependency not meta-python")
127
128 @skipIfNoNetwork()
129 def test_find_collection(self):
130 def _check(collection, expected):
131 self.logger.debug("Looking for collection %s..." % collection)
132 result = self.layerindex.find_collection(collection)
133 if expected:
134 self.assertIsNotNone(result, msg="Did not find %s when it should be there" % collection)
135 else:
136 self.assertIsNone(result, msg="Found %s when it shouldn't be there" % collection)
137
138 tests = [ ('core', True),
139 ('openembedded-core', False),
140 ('networking-layer', True),
141 ('meta-python', True),
142 ('openembedded-layer', True),
143 ('notpresent', False) ]
144
145 for collection,result in tests:
146 _check(collection, result)
147
148 @skipIfNoNetwork()
149 def test_find_layerbranch(self):
150 def _check(name, expected):
151 self.logger.debug("Looking for layerbranch %s..." % name)
152
153 for index in self.layerindex.indexes:
154 for layerbranchid in index.layerBranches:
155 self.logger.debug("Present: %s" % index.layerBranches[layerbranchid].layer.name)
156 result = self.layerindex.find_layerbranch(name)
157 if expected:
158 self.assertIsNotNone(result, msg="Did not find %s when it should be there" % collection)
159 else:
160 self.assertIsNone(result, msg="Found %s when it shouldn't be there" % collection)
161
162 tests = [ ('openembedded-core', True),
163 ('core', False),
164 ('meta-networking', True),
165 ('meta-python', True),
166 ('meta-oe', True),
167 ('notpresent', False) ]
168
169 for collection,result in tests:
170 _check(collection, result)
171
diff --git a/bitbake/lib/layerindexlib/tests/testdata/README b/bitbake/lib/layerindexlib/tests/testdata/README
deleted file mode 100644
index 36ab40bebe..0000000000
--- a/bitbake/lib/layerindexlib/tests/testdata/README
+++ /dev/null
@@ -1,11 +0,0 @@
1This test data is used to verify the 'cooker' module of the layerindex.
2
3The module consists of a faux project bblayers.conf with four layers defined.
4
5layer1 - openembedded-core
6layer2 - networking-layer
7layer3 - meta-python
8layer4 - openembedded-layer (meta-oe)
9
10Since we do not have a fully populated cooker, we use this to test the
11basic index generation, and not any deep recipe based contents.
diff --git a/bitbake/lib/layerindexlib/tests/testdata/build/conf/bblayers.conf b/bitbake/lib/layerindexlib/tests/testdata/build/conf/bblayers.conf
deleted file mode 100644
index 40429b2f66..0000000000
--- a/bitbake/lib/layerindexlib/tests/testdata/build/conf/bblayers.conf
+++ /dev/null
@@ -1,15 +0,0 @@
1LAYERSERIES_CORENAMES = "sumo"
2
3# LAYER_CONF_VERSION is increased each time build/conf/bblayers.conf
4# changes incompatibly
5LCONF_VERSION = "7"
6
7BBPATH = "${TOPDIR}"
8BBFILES ?= ""
9
10BBLAYERS ?= " \
11 ${TOPDIR}/layer1 \
12 ${TOPDIR}/layer2 \
13 ${TOPDIR}/layer3 \
14 ${TOPDIR}/layer4 \
15 "
diff --git a/bitbake/lib/layerindexlib/tests/testdata/layer1/conf/layer.conf b/bitbake/lib/layerindexlib/tests/testdata/layer1/conf/layer.conf
deleted file mode 100644
index 966d531959..0000000000
--- a/bitbake/lib/layerindexlib/tests/testdata/layer1/conf/layer.conf
+++ /dev/null
@@ -1,17 +0,0 @@
1# We have a conf and classes directory, add to BBPATH
2BBPATH .= ":${LAYERDIR}"
3# We have recipes-* directories, add to BBFILES
4BBFILES += "${LAYERDIR}/recipes-*/*/*.bb"
5
6BBFILE_COLLECTIONS += "core"
7BBFILE_PATTERN_core = "^${LAYERDIR}/"
8BBFILE_PRIORITY_core = "5"
9
10LAYERSERIES_CORENAMES = "sumo"
11
12# This should only be incremented on significant changes that will
13# cause compatibility issues with other layers
14LAYERVERSION_core = "11"
15LAYERSERIES_COMPAT_core = "sumo"
16
17BBLAYERS_LAYERINDEX_NAME_core = "openembedded-core"
diff --git a/bitbake/lib/layerindexlib/tests/testdata/layer2/conf/layer.conf b/bitbake/lib/layerindexlib/tests/testdata/layer2/conf/layer.conf
deleted file mode 100644
index 7569d1c217..0000000000
--- a/bitbake/lib/layerindexlib/tests/testdata/layer2/conf/layer.conf
+++ /dev/null
@@ -1,20 +0,0 @@
1# We have a conf and classes directory, add to BBPATH
2BBPATH .= ":${LAYERDIR}"
3
4# We have a packages directory, add to BBFILES
5BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \
6 ${LAYERDIR}/recipes-*/*/*.bbappend"
7
8BBFILE_COLLECTIONS += "networking-layer"
9BBFILE_PATTERN_networking-layer := "^${LAYERDIR}/"
10BBFILE_PRIORITY_networking-layer = "5"
11
12# This should only be incremented on significant changes that will
13# cause compatibility issues with other layers
14LAYERVERSION_networking-layer = "1"
15
16LAYERDEPENDS_networking-layer = "core"
17LAYERDEPENDS_networking-layer += "openembedded-layer"
18LAYERDEPENDS_networking-layer += "meta-python"
19
20LAYERSERIES_COMPAT_networking-layer = "sumo"
diff --git a/bitbake/lib/layerindexlib/tests/testdata/layer3/conf/layer.conf b/bitbake/lib/layerindexlib/tests/testdata/layer3/conf/layer.conf
deleted file mode 100644
index 7089071faf..0000000000
--- a/bitbake/lib/layerindexlib/tests/testdata/layer3/conf/layer.conf
+++ /dev/null
@@ -1,19 +0,0 @@
1# We might have a conf and classes directory, append to BBPATH
2BBPATH .= ":${LAYERDIR}"
3
4# We have recipes directories, add to BBFILES
5BBFILES += "${LAYERDIR}/recipes*/*/*.bb ${LAYERDIR}/recipes*/*/*.bbappend"
6
7BBFILE_COLLECTIONS += "meta-python"
8BBFILE_PATTERN_meta-python := "^${LAYERDIR}/"
9BBFILE_PRIORITY_meta-python = "7"
10
11# This should only be incremented on significant changes that will
12# cause compatibility issues with other layers
13LAYERVERSION_meta-python = "1"
14
15LAYERDEPENDS_meta-python = "core openembedded-layer"
16
17LAYERSERIES_COMPAT_meta-python = "sumo"
18
19LICENSE_PATH += "${LAYERDIR}/licenses"
diff --git a/bitbake/lib/layerindexlib/tests/testdata/layer4/conf/layer.conf b/bitbake/lib/layerindexlib/tests/testdata/layer4/conf/layer.conf
deleted file mode 100644
index 6649ee0208..0000000000
--- a/bitbake/lib/layerindexlib/tests/testdata/layer4/conf/layer.conf
+++ /dev/null
@@ -1,22 +0,0 @@
1# We have a conf and classes directory, append to BBPATH
2BBPATH .= ":${LAYERDIR}"
3
4# We have a recipes directory, add to BBFILES
5BBFILES += "${LAYERDIR}/recipes-*/*/*.bb ${LAYERDIR}/recipes-*/*/*.bbappend"
6
7BBFILE_COLLECTIONS += "openembedded-layer"
8BBFILE_PATTERN_openembedded-layer := "^${LAYERDIR}/"
9
10# Define the priority for recipes (.bb files) from this layer,
11# choosing carefully how this layer interacts with all of the
12# other layers.
13
14BBFILE_PRIORITY_openembedded-layer = "6"
15
16# This should only be incremented on significant changes that will
17# cause compatibility issues with other layers
18LAYERVERSION_openembedded-layer = "1"
19
20LAYERDEPENDS_openembedded-layer = "core"
21
22LAYERSERIES_COMPAT_openembedded-layer = "sumo"