[bitbake-devel] [PATCH 2/5] layerindexlib: Initial layer index processing module implementation
Mark Hatle
mark.hatle at windriver.com
Thu Jul 12 20:34:10 UTC 2018
The layer index module is expected to be used by various parts of the system
in order to access a layerindex-web (such as layers.openembedded.org) and
perform basic processing on the information, such as dependency scanning.
Along with the layerindex implementation are associated tests. The tests
properly honor BB_SKIP_NETTESTS='yes' to prevent test failures.
Tests Implemented:
- Branch, LayerItem, LayerBranch, LayerDependency, Recipe, Machine and
Distro objects
- LayerIndex setup using the layers.openembedded.org restapi
- LayerIndex storing and retrieving from a file
- LayerIndex verify dependency resolution ordering
- LayerIndex setup using simulated cooker data
Signed-off-by: Mark Hatle <mark.hatle at windriver.com>
---
bin/bitbake-selftest | 6 +-
lib/layerindexlib/README | 28 +
lib/layerindexlib/__init__.py | 974 +++++++++++++++++++++
lib/layerindexlib/common.py | 161 ++++
lib/layerindexlib/cooker.py | 338 +++++++
lib/layerindexlib/restapi.py | 375 ++++++++
lib/layerindexlib/tests/__init__.py | 0
lib/layerindexlib/tests/common.py | 37 +
lib/layerindexlib/tests/cooker.py | 125 +++
lib/layerindexlib/tests/layerindex.py | 233 +++++
lib/layerindexlib/tests/restapi.py | 170 ++++
lib/layerindexlib/tests/testdata/README | 11 +
.../tests/testdata/build/conf/bblayers.conf | 15 +
.../tests/testdata/layer1/conf/layer.conf | 17 +
.../tests/testdata/layer2/conf/layer.conf | 20 +
.../tests/testdata/layer3/conf/layer.conf | 19 +
.../tests/testdata/layer4/conf/layer.conf | 22 +
17 files changed, 2550 insertions(+), 1 deletion(-)
create mode 100644 lib/layerindexlib/README
create mode 100644 lib/layerindexlib/__init__.py
create mode 100644 lib/layerindexlib/common.py
create mode 100644 lib/layerindexlib/cooker.py
create mode 100644 lib/layerindexlib/restapi.py
create mode 100644 lib/layerindexlib/tests/__init__.py
create mode 100644 lib/layerindexlib/tests/common.py
create mode 100644 lib/layerindexlib/tests/cooker.py
create mode 100644 lib/layerindexlib/tests/layerindex.py
create mode 100644 lib/layerindexlib/tests/restapi.py
create mode 100644 lib/layerindexlib/tests/testdata/README
create mode 100644 lib/layerindexlib/tests/testdata/build/conf/bblayers.conf
create mode 100644 lib/layerindexlib/tests/testdata/layer1/conf/layer.conf
create mode 100644 lib/layerindexlib/tests/testdata/layer2/conf/layer.conf
create mode 100644 lib/layerindexlib/tests/testdata/layer3/conf/layer.conf
create mode 100644 lib/layerindexlib/tests/testdata/layer4/conf/layer.conf
diff --git a/bin/bitbake-selftest b/bin/bitbake-selftest
index afe1603..7ead688 100755
--- a/bin/bitbake-selftest
+++ b/bin/bitbake-selftest
@@ -22,6 +22,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib
import unittest
try:
import bb
+ import layerindexlib
except RuntimeError as exc:
sys.exit(str(exc))
@@ -31,7 +32,10 @@ tests = ["bb.tests.codeparser",
"bb.tests.event",
"bb.tests.fetch",
"bb.tests.parse",
- "bb.tests.utils"]
+ "bb.tests.utils",
+ "layerindexlib.tests.layerindex",
+ "layerindexlib.tests.restapi",
+ "layerindexlib.tests.cooker"]
for t in tests:
t = '.'.join(t.split('.')[:3])
diff --git a/lib/layerindexlib/README b/lib/layerindexlib/README
new file mode 100644
index 0000000..5d927af
--- /dev/null
+++ b/lib/layerindexlib/README
@@ -0,0 +1,28 @@
+The layerindexlib module is designed to permit programs to work directly
+with layer index information. (See layers.openembedded.org...)
+
+The layerindexlib module includes a plugin interface that is used to extend
+the basic functionality. There are two primary plugins available: restapi
+and cooker.
+
+The restapi plugin works with a web based REST Api compatible with the
+layerindex-web project, as well as the ability to store and retried a
+the information for one or more files on the disk.
+
+The cooker plugin works by reading the information from the current build
+project and processing it as if it were a layer index.
+
+
+TODO:
+
+__init__.py:
+Implement local on-disk caching (using the rest api store/load)
+Implement layer index style query operations on a combined index
+
+common.py:
+Stop network access if BB_NO_NETWORK or allowed hosts is restricted
+
+cooker.py:
+Cooker - Implement recipe parsing
+
+
diff --git a/lib/layerindexlib/__init__.py b/lib/layerindexlib/__init__.py
new file mode 100644
index 0000000..96644a3
--- /dev/null
+++ b/lib/layerindexlib/__init__.py
@@ -0,0 +1,974 @@
+# Copyright (C) 2016-2018 Wind River Systems, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import datetime
+
+import logging
+import imp
+
+import bb.fetch2
+
+from collections import OrderedDict
+
+logger = logging.getLogger('BitBake.layerindexlib')
+
+class LayerIndex():
+ def __init__(self, d):
+ if d:
+ self.data = d
+ else:
+ import bb.data
+ self.data = bb.data.init()
+ # We need to use the fetcher to parse the URL
+ # it requires DL_DIR to be set
+ self.data.setVar('DL_DIR', os.getcwd())
+
+ self.lindex = []
+
+ self.plugins = []
+
+ import bb.utils
+ bb.utils.load_plugins(logger, self.plugins, os.path.dirname(__file__))
+ for plugin in self.plugins:
+ if hasattr(plugin, 'init'):
+ plugin.init(self)
+
+ def __add__(self, other):
+ newIndex = LayerIndex(self.data)
+
+ if self.__class__ != newIndex.__class__ or \
+ other.__class__ != newIndex.__class__:
+ raise TypeException("Can not add different types.")
+
+ for lindexEnt in self.lindex:
+ newIndex.lindex.append(lindexEnt)
+
+ for lindexEnt in other.lindex:
+ newIndex.lindex.append(lindexEnt)
+
+ return newIndex
+
+ def _get_plugin(self, type):
+ for plugin in self.plugins:
+ if hasattr(plugin, 'plugin_type'):
+ plugintype = plugin.plugin_type()
+ logger.debug(1, "Looking for IndexPlugin - %s ? %s" % (plugintype, type))
+ if plugintype and plugintype == type:
+ return plugin
+ return None
+
+ loadRecipes = 1
+ def load_layerindex(self, indexURIs, reload=False, load='layerDependencies recipes machines distros'):
+ """Load the layerindex.
+
+indexURIs- This may be one or more indexes (white space seperated).
+
+reload - If reload is True, then any previously loaded indexes will be forgotten.
+
+load - Ability to NOT load certain elements for performance. White space seperated list
+ of optional things to load. (branches, layerItems and layerBranches is always
+ loaded.) Note: the plugins are permitted to ignore this and load everything.
+
+The format of the indexURI:
+
+ <url>;type=<type>;branch=<branch>;cache=<cache>;desc=<description>
+
+ Note: the 'branch' parameter if set can select multiple branches by using
+ comma, such as 'branch=master,morty,pyro'. However, many operations only look
+ at the -first- branch specified!
+
+ The cache value may be undefined, in this case a network failure will
+ result in an error, otherwise the system will look for a file of the cache
+ name and load that instead.
+
+ For example:
+
+ http://layers.openembedded.org/layerindex/api/;type=restapi;branch=master;desc=OpenEmbedded%20Layer%20Index
+ file://conf/bblayers.conf;type=internal
+
+restapi is either a web url or a local file or a local directory with one
+or more .json file in it in the restapi format
+
+internal refers to any layers loaded as part of a project conf/bblayers.conf
+"""
+ if reload:
+ self.lindex = []
+
+ logger.debug(1, 'Loading: %s' % indexURIs)
+
+ for url in indexURIs.split():
+ ud = bb.fetch2.FetchData(url, self.data)
+
+ if 'type' not in ud.parm:
+ raise bb.fetch2.MissingParameterError('type', url)
+
+ plugin = self._get_plugin(ud.parm['type'] or "restapi")
+
+ if not plugin:
+ raise NotImplementedError("%s: type %s is not available" % (url, ud.parm['type']))
+
+ # TODO: Implement 'cache', for when the network is not available
+ lindexEnt = plugin.load_index(ud, load)
+
+ if 'CONFIG' not in lindexEnt:
+ raise Exception('Internal Error: Missing configuration data in index %s' % url)
+
+ # Mark CONFIG data as something we've added...
+ lindexEnt['CONFIG']['local'] = []
+ lindexEnt['CONFIG']['local'].append('CONFIG')
+
+ if 'branches' not in lindexEnt:
+ raise Exception('Internal Error: No branches defined in index %s' % url)
+
+ # Create quick lookup layerBranches_layerId_branchId table
+ if 'layerBranches' in lindexEnt:
+ # Create associated quick lookup indexes
+ lindexEnt['layerBranches_layerId_branchId'] = {}
+ for layerBranchId in lindexEnt['layerBranches']:
+ obj = lindexEnt['layerBranches'][layerBranchId]
+ lindexEnt['layerBranches_layerId_branchId']["%s:%s" % (obj.get_layer_id(), obj.get_branch_id())] = obj
+ # Mark layerBranches_layerId_branchId as something we added
+ lindexEnt['CONFIG']['local'].append('layerBranches_layerId_branchId')
+
+ # Create quick lookup layerDependencies_layerBranchId table
+ if 'layerDependencies' in lindexEnt:
+ # Create associated quick lookup indexes
+ lindexEnt['layerDependencies_layerBranchId'] = {}
+ for layerDependencyId in lindexEnt['layerDependencies']:
+ obj = lindexEnt['layerDependencies'][layerDependencyId]
+ if obj.get_layerbranch_id() not in lindexEnt['layerDependencies_layerBranchId']:
+ lindexEnt['layerDependencies_layerBranchId'][obj.get_layerbranch_id()] = [obj]
+ else:
+ lindexEnt['layerDependencies_layerBranchId'][obj.get_layerbranch_id()].append(obj)
+ # Mark layerDependencies_layerBranchId as something we added
+ lindexEnt['CONFIG']['local'].append('layerDependencies_layerBranchId')
+
+ # Create quick lookup layerUrls
+ if 'layerBranches' in lindexEnt:
+ # Create associated quick lookup indexes
+ lindexEnt['layerUrls'] = {}
+ for layerBranchId in lindexEnt['layerBranches']:
+ obj = lindexEnt['layerBranches'][layerBranchId]
+ vcs_url = obj.get_layer().get_vcs_url()
+ if vcs_url not in lindexEnt['layerUrls']:
+ lindexEnt['layerUrls'][vcs_url] = [obj]
+ else:
+ # We insert this if there is no subdir, we know it's the parent
+ if not obj.get_vcs_subdir():
+ lindexEnt['layerUrls'][vcs_url].insert(0, obj)
+ else:
+ lindexEnt['layerUrls'][vcs_url].append(obj)
+ # Mark layerUrls as something we added
+ lindexEnt['CONFIG']['local'].append('layerUrls')
+
+ self.lindex.append(lindexEnt)
+
+ def store_layerindex(self, indexURI, lindex=None):
+ """Store a layerindex
+
+Typically this will be used to create a local cache file of a remote index.
+
+ file://<path>;type=<type>;branch=<branch>
+
+We can write out in either the restapi or django formats. The split option
+will write out the individual elements split by layer and related components.
+"""
+ if not lindex:
+ logger.warning('No index to write, nothing to do.')
+ return
+
+ ud = bb.fetch2.FetchData(indexURI, self.data)
+
+ if 'type' not in ud.parm:
+ raise bb.fetch2.MissingParameterError('type', indexURI)
+
+ plugin = self._get_plugin(ud.parm['type'])
+
+ if not plugin:
+ raise NotImplementedError("%s: type %s is not available" % (url, ud.parm['type']))
+
+ lindexEnt = plugin.store_index(ud, lindex)
+
+
+ def get_json_query(self, query):
+ """Return a query in restapi format
+
+This is a compatibility function. It will acts like the web restapi query
+and return back the information related to a specific query. It can be used
+but other components of the system that would rather deal with restapi
+style queries then the regular functions in this class.
+
+Note: only select queries are supported. This will have to be expanded
+to support additional queries.
+
+This function will merge multiple databases together to return a single
+coherent 'superset' result, when more then one index has been loaded.
+"""
+
+ # TODO Implement get_json_query
+ raise Exception("get_json_query: not Implemented!")
+
+ def is_empty(self):
+ """Return True or False if the index has any usable data.
+
+We check the lindex entries to see if they have a branch set, as well as
+layerBranches set. If not, they are effectively blank."""
+
+ found = False
+ for lindex in self.lindex:
+ if 'branches' in lindex and 'layerBranches' in lindex and \
+ lindex['branches'] and lindex['layerBranches']:
+ found = True
+ break
+ return not found
+
+
+ def find_vcs_url(self, vcs_url, branch=None):
+ """Return the first layerBranch with the given vcs_url
+
+If a branch has not been specified, we will iterate over the branches in
+the default configuration until the first vcs_url/branch match."""
+
+ for lindex in self.lindex:
+ logger.debug(1, ' searching %s' % lindex['CONFIG']['DESCRIPTION'])
+ layerBranch = self._find_vcs_url(lindex, vcs_url, branch)
+ if layerBranch:
+ return layerBranch
+ return None
+
+ def _find_vcs_url(self, lindex, vcs_url, branch=None):
+ if 'branches' not in lindex or 'layerBranches' not in lindex:
+ return None
+
+ if vcs_url in lindex['layerUrls']:
+ for layerBranch in lindex['layerUrls'][vcs_url]:
+ if branch and branch == layerBranch.get_branch().get_name():
+ return layerBranch
+ if not branch:
+ return layerBranch
+
+ return None
+
+
+ def find_collection(self, collection, version=None, branch=None):
+ """Return the first layerBranch with the given collection name
+
+If a branch has not been specified, we will iterate over the branches in
+the default configuration until the first colelction/branch match."""
+
+ logger.debug(1, 'find_collection: %s (%s) %s' % (collection, version, branch))
+
+ for lindex in self.lindex:
+ logger.debug(1, ' searching %s' % lindex['CONFIG']['DESCRIPTION'])
+ layerBranch = self._find_collection(lindex, collection, version, branch)
+ if layerBranch:
+ return layerBranch
+ else:
+ logger.debug(1, 'Collection %s (%s) not found for branch (%s)' % (collection, version, branch))
+ return None
+
+ def _find_collection(self, lindex, collection, version=None, branch=None):
+ if 'branches' not in lindex or 'layerBranches' not in lindex:
+ return None
+
+ def find_branch_layerItem(branch, collection, version):
+ for branchId in lindex['branches']:
+ if branch == lindex['branches'][branchId].get_name():
+ break
+ else:
+ return None
+
+ for layerBranchId in lindex['layerBranches']:
+ if branchId == lindex['layerBranches'][layerBranchId].get_branch_id() and \
+ collection == lindex['layerBranches'][layerBranchId].get_collection():
+ if not version or version == lindex['layerBranches'][layerBranchId].get_version():
+ return lindex['layerBranches'][layerBranchId]
+
+ return None
+
+ if branch:
+ layerBranch = find_branch_layerItem(branch, collection, version)
+ return layerBranch
+
+ # No branch, so we have to scan the branches in order...
+ # Use the config order if we have it...
+ if 'CONFIG' in lindex and 'BRANCH' in lindex['CONFIG']:
+ for branch in lindex['CONFIG']['BRANCH'].split(','):
+ layerBranch = find_branch_layerItem(branch, collection, version)
+ if layerBranch:
+ return layerBranch
+
+ # ...use the index order if we don't...
+ else:
+ for branchId in lindex['branches']:
+ branch = lindex['branches'][branchId].get_name()
+ layerBranch = get_branch_layerItem(branch, collection, version)
+ if layerBranch:
+ return layerBranch
+
+ return None
+
+
+ def get_layerbranch(self, name, branch=None):
+ """Return the layerBranch item for a given name and branch
+
+If a branch has not been specified, we will iterate over the branches in
+the default configuration until the first name/branch match."""
+
+ for lindex in self.lindex:
+ layerBranch = self._get_layerbranch(lindex, name, branch)
+ if layerBranch:
+ return layerBranch
+ return None
+
+ def _get_layerbranch(self, lindex, name, branch=None):
+ if 'branches' not in lindex or 'layerItems' not in lindex:
+ logger.debug(1, 'No branches or no layerItems in lindex %s' % (lindex['CONFIG']['DESCRIPTION']))
+ return None
+
+ def get_branch_layerItem(branch, name):
+ for branchId in lindex['branches']:
+ if branch == lindex['branches'][branchId].get_name():
+ break
+ else:
+ return None
+
+ for layerItemId in lindex['layerItems']:
+ if name == lindex['layerItems'][layerItemId].get_name():
+ break
+ else:
+ return None
+
+ key = "%s:%s" % (layerItemId, branchId)
+ if key in lindex['layerBranches_layerId_branchId']:
+ return lindex['layerBranches_layerId_branchId'][key]
+ return None
+
+ if branch:
+ layerBranch = get_branch_layerItem(branch, name)
+ return layerBranch
+
+ # No branch, so we have to scan the branches in order...
+ # Use the config order if we have it...
+ if 'CONFIG' in lindex and 'BRANCH' in lindex['CONFIG']:
+ for branch in lindex['CONFIG']['BRANCH'].split(','):
+ layerBranch = get_branch_layerItem(branch, name)
+ if layerBranch:
+ return layerBranch
+
+ # ...use the index order if we don't...
+ else:
+ for branchId in lindex['branches']:
+ branch = lindex['branches'][branchId].get_name()
+ layerBranch = get_branch_layerItem(branch, name)
+ if layerBranch:
+ return layerBranch
+ return None
+
+ def get_dependencies(self, names=None, layerBranches=None, ignores=None):
+ """Return a tuple of all dependencies and invalid items.
+
+The dependency scanning happens with a depth-first approach, so the returned
+dependencies should be in the best order to define a bblayers.
+
+names - a space deliminated list of layerItem names.
+Branches are resolved in the order of the specified index's load. Subsequent
+branch resolution is on the same branch.
+
+layerBranches - a list of layerBranches to resolve dependencies
+Branches are the same as the passed in layerBranch.
+
+ignores - a list of layer names to ignore
+
+Return value: (dependencies, invalid)
+
+dependencies is an orderedDict, with the key being the layer name.
+The value is a list with the first ([0]) being the layerBranch, and subsequent
+items being the layerDependency entries that caused this to be added.
+
+invalid is just a list of dependencies that were not found.
+"""
+ invalid = []
+
+ if not layerBranches:
+ layerBranches = []
+
+ if names:
+ for name in names.split():
+ if ignores and name in ignores:
+ continue
+
+ # Since we don't have a branch, we have to just find the first
+ # layerBranch with that name...
+ for lindex in self.lindex:
+ layerBranch = self._get_layerbranch(lindex, name)
+ if not layerBranch:
+ # Not in this index, hopefully it's in another...
+ continue
+
+ if layerBranch not in layerBranches:
+ layerBranches.append(layerBranch)
+ break
+ else:
+ logger.warning("Layer %s not found. Marked as invalid." % name)
+ invalid.append(name)
+ layerBranch = None
+
+ # Format is required['name'] = [ layer_branch, dependency1, dependency2, ..., dependencyN ]
+ dependencies = OrderedDict()
+ (dependencies, invalid) = self._get_dependencies(layerBranches, ignores, dependencies, invalid)
+
+ for layerBranch in layerBranches:
+ if layerBranch.get_layer().get_name() not in dependencies:
+ dependencies[layerBranch.get_layer().get_name()] = [layerBranch]
+
+ return (dependencies, invalid)
+
+
+ def _get_dependencies(self, layerBranches, ignores, dependencies, invalid):
+ for layerBranch in layerBranches:
+ name = layerBranch.get_layer().get_name()
+ # Do we ignore it?
+ if ignores and name in ignores:
+ continue
+
+ if 'layerDependencies_layerBranchId' not in layerBranch.index:
+ raise Exception('Missing layerDepedencies_layerBranchId cache! %s' % layerBranch.index['CONFIG']['DESCRIPTION'])
+
+ # Get a list of dependencies and then recursively process them
+ if layerBranch.get_id() in layerBranch.index['layerDependencies_layerBranchId']:
+ for layerDependency in layerBranch.index['layerDependencies_layerBranchId'][layerBranch.get_id()]:
+ depLayerBranch = layerDependency.get_dependency_layerBranch()
+
+ # Do we need to resolve across indexes?
+ if depLayerBranch.index != self.lindex[0]:
+ rdepLayerBranch = self.find_collection(
+ collection=depLayerBranch.get_collection(),
+ version=depLayerBranch.get_version()
+ )
+ if rdepLayerBranch != depLayerBranch:
+ logger.debug(1, 'Replaced %s:%s:%s with %s:%s:%s' % \
+ (depLayerBranch.index['CONFIG']['DESCRIPTION'],
+ depLayerBranch.get_branch().get_name(),
+ depLayerBranch.get_layer().get_name(),
+ rdepLayerBranch.index['CONFIG']['DESCRIPTION'],
+ rdepLayerBranch.get_branch().get_name(),
+ rdepLayerBranch.get_layer().get_name()))
+ depLayerBranch = rdepLayerBranch
+
+ # Is this dependency on the list to be ignored?
+ if ignores and depLayerBranch.get_layer().get_name() in ignores:
+ continue
+
+ # Previously found dependencies have been processed, as
+ # have their dependencies...
+ if depLayerBranch.get_layer().get_name() not in dependencies:
+ (dependencies, invalid) = self._get_dependencies([depLayerBranch], ignores, dependencies, invalid)
+
+ if depLayerBranch.get_layer().get_name() not in dependencies:
+ dependencies[depLayerBranch.get_layer().get_name()] = [depLayerBranch, layerDependency]
+ else:
+ if layerDependency not in dependencies[depLayerBranch.get_layer().get_name()]:
+ dependencies[depLayerBranch.get_layer().get_name()].append(layerDependency)
+
+ return (dependencies, invalid)
+
+
+ def list_obj(self, object):
+ """Print via the plain logger object information
+
+This function is used to implement debugging and provide the user info.
+"""
+ for lix in self.lindex:
+ if object not in lix:
+ continue
+
+ logger.plain ('')
+ logger.plain('Index: %s' % lix['CONFIG']['DESCRIPTION'])
+
+ output = []
+
+ if object == 'branches':
+ logger.plain ('%s %s %s' % ('{:26}'.format('branch'), '{:34}'.format('description'), '{:22}'.format('bitbake branch')))
+ logger.plain ('{:-^80}'.format(""))
+ for branchId in lix['branches']:
+ output.append('%s %s %s' % (
+ '{:26}'.format(lix['branches'][branchId].get_name()),
+ '{:34}'.format(lix['branches'][branchId].get_short_description()),
+ '{:22}'.format(lix['branches'][branchId].get_bitbake_branch())
+ ))
+ for line in sorted(output):
+ logger.plain (line)
+
+ continue
+
+ if object == 'layerItems':
+ logger.plain ('%s %s' % ('{:26}'.format('layer'), '{:34}'.format('description')))
+ logger.plain ('{:-^80}'.format(""))
+ for layerId in lix['layerItems']:
+ output.append('%s %s' % (
+ '{:26}'.format(lix['layerItems'][layerId].get_name()),
+ '{:34}'.format(lix['layerItems'][layerId].get_summary())
+ ))
+ for line in sorted(output):
+ logger.plain (line)
+
+ continue
+
+ if object == 'layerBranches':
+ logger.plain ('%s %s %s' % ('{:26}'.format('layer'), '{:34}'.format('description'), '{:19}'.format('collection:version')))
+ logger.plain ('{:-^80}'.format(""))
+ for layerBranchId in lix['layerBranches']:
+ output.append('%s %s %s' % (
+ '{:26}'.format(lix['layerBranches'][layerBranchId].get_layer().get_name()),
+ '{:34}'.format(lix['layerBranches'][layerBranchId].get_layer().get_summary()),
+ '{:19}'.format("%s:%s" %
+ (lix['layerBranches'][layerBranchId].get_collection(),
+ lix['layerBranches'][layerBranchId].get_version())
+ )
+ ))
+ for line in sorted(output):
+ logger.plain (line)
+
+ continue
+
+ if object == 'layerDependencies':
+ logger.plain ('%s %s %s %s' % ('{:19}'.format('branch'), '{:26}'.format('layer'), '{:11}'.format('dependency'), '{:26}'.format('layer')))
+ logger.plain ('{:-^80}'.format(""))
+ for layerDependency in lix['layerDependencies']:
+ if not lix['layerDependencies'][layerDependency].get_dependency_layerBranch():
+ continue
+
+ output.append('%s %s %s %s' % (
+ '{:19}'.format(lix['layerDependencies'][layerDependency].get_layerbranch().get_branch().get_name()),
+ '{:26}'.format(lix['layerDependencies'][layerDependency].get_layerbranch().get_layer().get_name()),
+ '{:11}'.format('requires' if lix['layerDependencies'][layerDependency].is_required() else 'recommends'),
+ '{:26}'.format(lix['layerDependencies'][layerDependency].get_dependency_layerBranch().get_layer().get_name())
+ ))
+ for line in sorted(output):
+ logger.plain (line)
+
+ continue
+
+ if object == 'recipes':
+ logger.plain ('%s %s %s' % ('{:20}'.format('recipe'), '{:10}'.format('version'), 'layer'))
+ logger.plain ('{:-^80}'.format(""))
+ output = []
+ for recipe in lix['recipes']:
+ output.append('%s %s %s' % (
+ '{:30}'.format(lix['recipes'][recipe].get_pn()),
+ '{:30}'.format(lix['recipes'][recipe].get_pv()),
+ lix['recipes'][recipe].get_layer().get_name()
+ ))
+ for line in sorted(output):
+ logger.plain (line)
+
+ continue
+
+ if object == 'machines':
+ logger.plain ('%s %s %s' % ('{:24}'.format('machine'), '{:34}'.format('description'), '{:19}'.format('layer')))
+ logger.plain ('{:-^80}'.format(""))
+ for machine in lix['machines']:
+ output.append('%s %s %s' % (
+ '{:24}'.format(lix['machines'][machine].get_name()),
+ ('{:34}'.format(lix['machines'][machine].get_description()))[:34],
+ '{:19}'.format(lix['machines'][machine].get_layerbranch().get_layer().get_name() )
+ ))
+ for line in sorted(output):
+ logger.plain (line)
+
+ continue
+
+ if object == 'distros':
+ logger.plain ('%s %s %s' % ('{:24}'.format('distro'), '{:34}'.format('description'), '{:19}'.format('layer')))
+ logger.plain ('{:-^80}'.format(""))
+ for distro in lix['distros']:
+ output.append('%s %s %s' % (
+ '{:24}'.format(lix['distros'][distro].get_name()),
+ ('{:34}'.format(lix['distros'][distro].get_description()))[:34],
+ '{:19}'.format(lix['distros'][distro].get_layerbranch().get_layer().get_name() )
+ ))
+ for line in sorted(output):
+ logger.plain (line)
+
+ continue
+
+ logger.plain ('')
+
+# Define enough of the layer index types so we can easily resolve them...
+# It is up to the loaders to create the classes from the raw data
+class LayerIndexItem():
+ def __init__(self, index, data):
+ self.index = index
+ self.data = data
+
+ def __eq__(self, other):
+ if self.__class__ != other.__class__:
+ return False
+ res=(self.data == other.data)
+ logger.debug(2, 'Compare objects: %s ? %s : %s' % (self.get_id(), other.get_id(), res))
+ return res
+
+ def define_data(self, id):
+ self.data = {}
+ self.data['id'] = id
+
+ def get_id(self):
+ return self.data['id']
+
+
+class Branch(LayerIndexItem):
+ def define_data(self, id, name, bitbake_branch,
+ short_description=None, sort_priority=1,
+ updates_enabled=True, updated=None,
+ update_environment=None):
+ self.data = {}
+ self.data['id'] = id
+ self.data['name'] = name
+ self.data['bitbake_branch'] = bitbake_branch
+ self.data['short_description'] = short_description or name
+ self.data['sort_priority'] = sort_priority
+ self.data['updates_enabled'] = updates_enabled
+ self.data['updated'] = updated or datetime.datetime.today().isoformat()
+ self.data['update_environment'] = update_environment
+
+ def get_name(self):
+ return self.data['name']
+
+ def get_short_description(self):
+ return self.data['short_description'].strip()
+
+ def get_bitbake_branch(self):
+ return self.data['bitbake_branch'] or self.get_name()
+
+
+class LayerItem(LayerIndexItem):
+ def define_data(self, id, name, status='P',
+ layer_type='A', summary=None,
+ description=None,
+ vcs_url=None, vcs_web_url=None,
+ vcs_web_tree_base_url=None,
+ vcs_web_file_base_url=None,
+ usage_url=None,
+ mailing_list_url=None,
+ index_preference=1,
+ classic=False,
+ updated=None):
+ self.data = {}
+ self.data['id'] = id
+ self.data['name'] = name
+ self.data['status'] = status
+ self.data['layer_type'] = layer_type
+ self.data['summary'] = summary or name
+ self.data['description'] = description or summary or name
+ self.data['vcs_url'] = vcs_url
+ self.data['vcs_web_url'] = vcs_web_url
+ self.data['vcs_web_tree_base_url'] = vcs_web_tree_base_url
+ self.data['vcs_web_file_base_url'] = vcs_web_file_base_url
+ self.data['index_preference'] = index_preference
+ self.data['classic'] = classic
+ self.data['updated'] = updated or datetime.datetime.today().isoformat()
+
+ def get_name(self):
+ return self.data['name']
+
+ def get_summary(self):
+ return self.data['summary']
+
+ def get_description(self):
+ return self.data['description'].strip()
+
+ def get_vcs_url(self):
+ return self.data['vcs_url']
+
+ def get_vcs_web_url(self):
+ return self.data['vcs_web_url']
+
+ def get_vcs_web_tree_base_url(self):
+ return self.data['vcs_web_tree_base_url']
+
+ def get_vcs_web_file_base_url(self):
+ return self.data['vcs_web_file_base_url']
+
+ def get_updated(self):
+ return self.data['updated']
+
+class LayerBranch(LayerIndexItem):
+ def define_data(self, id, collection, version, layer, branch,
+ vcs_subdir="", vcs_last_fetch=None,
+ vcs_last_rev=None, vcs_last_commit=None,
+ actual_branch="",
+ updated=None):
+ self.data = {}
+ self.data['id'] = id
+ self.data['collection'] = collection
+ self.data['version'] = version
+ self.data['layer'] = layer
+ self.data['branch'] = branch
+ self.data['vcs_subdir'] = vcs_subdir
+ self.data['vcs_last_fetch'] = vcs_last_fetch
+ self.data['vcs_last_rev'] = vcs_last_rev
+ self.data['vcs_last_commit'] = vcs_last_commit
+ self.data['actual_branch'] = actual_branch
+ self.data['updated'] = updated or datetime.datetime.today().isoformat()
+
+ def get_collection(self):
+ return self.data['collection']
+
+ def get_version(self):
+ return self.data['version']
+
+ def get_vcs_subdir(self):
+ return self.data['vcs_subdir']
+
+ def get_actual_branch(self):
+ return self.data['actual_branch'] or self.get_branch().get_name()
+
+ def get_updated(self):
+ return self.data['updated']
+
+ def get_layer_id(self):
+ return self.data['layer']
+
+ def get_branch_id(self):
+ return self.data['branch']
+
+ def get_layer(self):
+ layerItem = None
+ try:
+ layerItem = self.index['layerItems'][self.get_layer_id()]
+ except KeyError:
+ logger.error('Unable to find layerItems in index')
+ except IndexError:
+ logger.error('Unable to find layerId %s' % self.get_layer_id())
+ return layerItem
+
+ def get_branch(self):
+ branch = None
+ try:
+ branch = self.index['branches'][self.get_branch_id()]
+ except KeyError:
+ logger.error('Unable to find branches in index: %s' % self.index.keys())
+ except IndexError:
+ logger.error('Unable to find branchId %s' % self.get_branch_id())
+ return branch
+
+
+class LayerIndexItem_LayerBranch(LayerIndexItem):
+ def get_layerbranch_id(self):
+ return self.data['layerbranch']
+
+ def get_layerbranch(self):
+ layerBranch = None
+ try:
+ layerBranch = self.index['layerBranches'][self.get_layerbranch_id()]
+ except KeyError:
+ logger.error('Unable to find layerBranches in index')
+ except IndexError:
+ logger.error('Unable to find layerBranchId %s' % self.get_layerbranch_id())
+ return layerBranch
+
+ def get_layer_id(self):
+ layerBranch = self.get_layerbranch()
+ if layerBranch:
+ return layerBranch.get_layer_id()
+ return None
+
+ def get_layer(self):
+ layerBranch = self.get_layerbranch()
+ if layerBranch:
+ return layerBranch.get_layer()
+ return None
+
+class LayerDependency(LayerIndexItem_LayerBranch):
+ def define_data(self, id, layerbranch, dependency, required=True):
+ self.data = {}
+ self.data['id'] = id
+ self.data['layerbranch'] = layerbranch
+ self.data['dependency'] = dependency
+ self.data['required'] = required
+
+ def is_required(self):
+ return self.data['required']
+
+ def get_dependency_id(self):
+ return self.data['dependency']
+
+ def get_dependency_layer(self):
+ layerItem = None
+ try:
+ layerItem = self.index['layerItems'][self.get_dependency_id()]
+ except KeyError:
+ logger.error('Unable to find layerItems in index')
+ except IndexError:
+ logger.error('Unable to find layerId %s' % self.get_dependency_id())
+ return layerItem
+
+ def get_dependency_layerBranch(self):
+ layerBranch = None
+ try:
+ layerId = self.get_dependency_id()
+ branchId = self.get_layerbranch().get_branch_id()
+ layerBranch = self.index['layerBranches_layerId_branchId']["%s:%s" % (layerId, branchId)]
+ except KeyError:
+ logger.warning('Unable to find layerBranches_layerId_branchId in index')
+
+ # We don't have a quick lookup index, doing it the slower way...
+ layerId = self.get_dependency_id()
+ branchId = self.get_layerbranch().get_branch_id()
+ for layerBranchId in self.index['layerBranches']:
+ layerBranch = self.index['layerBranches'][layerBranchId]
+ if layerBranch.get_layer_id() == layerId and \
+ layerBranch.get_branch_id() == branchId:
+ break
+ else:
+ logger.error("LayerBranch not found layerId %s -- BranchId %s" % (layerId, branchId))
+ layerBranch = None
+ except IndexError:
+ logger.error("LayerBranch not found layerId %s -- BranchId %s" % (layerId, branchId))
+
+ return layerBranch
+
+
+class Recipe(LayerIndexItem_LayerBranch):
+ def define_data(self, id,
+ filename, filepath, pn, pv, layerbranch,
+ summary="", description="", section="", license="",
+ homepage="", bugtracker="", provides="", bbclassextend="",
+ inherits="", blacklisted="", updated=None):
+ self.data = {}
+ self.data['id'] = id
+ self.data['filename'] = filename
+ self.data['filepath'] = filepath
+ self.data['pn'] = pn
+ self.data['pv'] = pv
+ self.data['summary'] = summary
+ self.data['description'] = description
+ self.data['section'] = section
+ self.data['license'] = license
+ self.data['homepage'] = homepage
+ self.data['bugtracker'] = bugtracker
+ self.data['provides'] = provides
+ self.data['bbclassextend'] = bbclassextend
+ self.data['inherits'] = inherits
+ self.data['updated'] = updated or datetime.datetime.today().isoformat()
+ self.data['blacklisted'] = blacklisted
+ self.data['layerbranch'] = layerbranch
+
+ def get_filename(self):
+ return self.data['filename']
+
+ def get_filepath(self):
+ return self.data['filepath']
+
+ def get_fullpath(self):
+ return os.path.join(self.data['filepath'], self.data['filename'])
+
+ def get_summary(self):
+ return self.data['summary']
+
+ def get_description(self):
+ return self.data['description'].strip()
+
+ def get_section(self):
+ return self.data['section']
+
+ def get_pn(self):
+ return self.data['pn']
+
+ def get_pv(self):
+ return self.data['pv']
+
+ def get_license(self):
+ return self.data['license']
+
+ def get_homepage(self):
+ return self.data['homepage']
+
+ def get_bugtracker(self):
+ return self.data['bugtracker']
+
+ def get_provides(self):
+ return self.data['provides']
+
+ def get_updated(self):
+ return self.data['updated']
+
+ def get_inherits(self):
+ if 'inherits' not in self.data:
+ # Older indexes may not have this, so emulate it
+ if '-image-' in self.get_pn():
+ return 'image'
+ return self.data['inherits']
+
+
+class Machine(LayerIndexItem_LayerBranch):
+ def define_data(self, id,
+ name, description, layerbranch,
+ updated=None):
+ self.data = {}
+ self.data['id'] = id
+ self.data['name'] = name
+ self.data['description'] = description
+ self.data['layerbranch'] = layerbranch
+ self.data['updated'] = updated or datetime.datetime.today().isoformat()
+
+ def get_name(self):
+ return self.data['name']
+
+ def get_description(self):
+ return self.data['description'].strip()
+
+ def get_updated(self):
+ return self.data['updated']
+
+class Distro(LayerIndexItem_LayerBranch):
+ def define_data(self, id,
+ name, description, layerbranch,
+ updated=None):
+ self.data = {}
+ self.data['id'] = id
+ self.data['name'] = name
+ self.data['description'] = description
+ self.data['layerbranch'] = layerbranch
+ self.data['updated'] = updated or datetime.datetime.today().isoformat()
+
+ def get_name(self):
+ return self.data['name']
+
+ def get_description(self):
+ return self.data['description'].strip()
+
+ def get_updated(self):
+ return self.data['updated']
+
+# When performing certain actions, we may need to sort the data.
+# This will allow us to keep it consistent from run to run.
+def sort_entry(item):
+ newitem = item
+ try:
+ if type(newitem) == type(dict()):
+ newitem = OrderedDict(sorted(newitem.items(), key=lambda t: t[0]))
+ for index in newitem:
+ newitem[index] = sort_entry(newitem[index])
+ elif type(newitem) == type(list()):
+ newitem.sort(key=lambda obj: obj['id'])
+ for index, _ in enumerate(newitem):
+ newitem[index] = sort_entry(newitem[index])
+ except:
+ logger.error('Sort failed for item %s' % type(item))
+ pass
+
+ return newitem
diff --git a/lib/layerindexlib/common.py b/lib/layerindexlib/common.py
new file mode 100644
index 0000000..b895916
--- /dev/null
+++ b/lib/layerindexlib/common.py
@@ -0,0 +1,161 @@
+# Copyright (C) 2016-2018 Wind River Systems, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+# The file contains:
+# LayerIndex exceptions
+# Plugin base class
+# Utility Functions for working on layerindex data
+
+import argparse
+import logging
+import os
+import bb.msg
+
+logger = logging.getLogger('BitBake.layerindexlib.common')
+
+class LayerIndexError(Exception):
+ """LayerIndex loading error"""
+ def __init__(self, message):
+ self.msg = message
+ Exception.__init__(self, message)
+
+ def __str__(self):
+ return self.msg
+
+class IndexPlugin():
+ def __init__(self):
+ self.type = None
+
+ def init(self, lindex):
+ self.lindex = lindex
+
+ def plugin_type(self):
+ return self.type
+
+ def load_index(self, uri):
+ raise NotImplementedError('load_index is not implemented')
+
+ def store_index(self, uri):
+ raise NotImplementedError('store_index is not implemented')
+
+# The following are some basic utility function used by the layerindex
+
+def fetch_url(url, username=None, password=None, debuglevel=0):
+ """
+ Fetch something from a specific URL. This is specifically designed to
+ fetch data from a layer index.
+
+ It is not designed to be used to fetch recipe sources or similar, the
+ regular fetcher is designed for that.
+
+ TODO: Handle BB_NO_NETWORK or allowed hosts, etc.
+ """
+
+ assert url is not None
+
+ import urllib
+ from urllib.request import urlopen, Request
+ from urllib.parse import urlparse
+
+ up = urlparse(url)
+
+ if username:
+ logger.debug(1, "Configuring authentication for %s..." % url)
+ password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
+ password_mgr.add_password(None, "%s://%s" % (up.scheme, up.netloc), username, password)
+ handler = urllib.request.HTTPBasicAuthHandler(password_mgr)
+ opener = urllib.request.build_opener(handler, urllib.request.HTTPSHandler(debuglevel=debuglevel))
+ else:
+ opener = urllib.request.build_opener(urllib.request.HTTPSHandler(debuglevel=debuglevel))
+
+ urllib.request.install_opener(opener)
+
+ logger.debug(1, "Fetching %s (%s)..." % (url, ["without authentication", "with authentication"][not not username]))
+
+ try:
+ res = urlopen(Request(url, headers={'User-Agent': 'Mozilla/5.0 (bitbake/lib/layerindex)'}, unverifiable=True))
+ except urllib.error.HTTPError as e:
+ logger.debug(1, "HTTP Error: %s: %s" % (e.code, e.reason))
+ logger.debug(1, " Requested: %s" % (url))
+ logger.debug(1, " Actual: %s" % (e.geturl()))
+
+ if e.code == 404:
+ logger.debug(1, "Request not found.")
+ raise bb.fetch2.FetchError(e)
+ else:
+ logger.debug(1, "Headers:\n%s" % (e.headers))
+ raise bb.fetch2.FetchError(e)
+ except OSError as e:
+ error = 0
+ reason = ""
+
+ # Process base OSError first...
+ if hasattr(e, 'errno'):
+ error = e.errno
+ reason = e.strerror
+
+ # Process gaierror (socket error) subclass if available.
+ if hasattr(e, 'reason') and hasattr(e.reason, 'errno') and hasattr(e.reason, 'strerror'):
+ error = e.reason.errno
+ reason = e.reason.strerror
+ if error == -2:
+ raise bb.fetch2.FetchError(e)
+
+ if error and error != 0:
+ raise bb.fetch2.FetchError("Unable to fetch %s due to exception: [Error %s] %s" % (url, error, reason))
+ else:
+ raise bb.fetch2.FetchError("Unable to fetch %s due to OSError exception: %s" % (url, e))
+
+ finally:
+ logger.debug(1, "...fetching %s (%s), done." % (url, ["without authentication", "with authentication"][not not username]))
+
+ return res
+
+def add_raw_element(lName, lType, rawObjs, lindex):
+ """
+ Add a raw object of type lType to lindex[lname]
+ """
+ if lName not in rawObjs:
+ logger.debug(1, '%s not in loaded index' % lName)
+ return lindex
+
+ if lName not in lindex:
+ lindex[lName] = {}
+
+ for entry in rawObjs[lName]:
+ obj = lType(lindex, entry)
+ if obj.get_id() in lindex[lName]:
+ if lindex[lName][obj.get_id()] == obj:
+ continue
+ raise Exception('Conflict adding object %s(%s)' % (lName, obj.get_id()))
+ lindex[lName][obj.get_id()] = obj
+
+ return lindex
+
+def add_element(lName, Objs, lindex):
+ """
+ Add a layer index object to lindex[lName]
+ """
+ if lName not in lindex:
+ lindex[lName] = {}
+
+ for obj in Objs:
+ if obj.get_id() in lindex[lName]:
+ if lindex[lName][obj.get_id()] == obj:
+ continue
+ raise Exception('Conflict adding object %s(%s)' % (lName, obj.get_id()))
+ lindex[lName][obj.get_id()] = obj
+
+ return lindex
diff --git a/lib/layerindexlib/cooker.py b/lib/layerindexlib/cooker.py
new file mode 100644
index 0000000..bdd37b0
--- /dev/null
+++ b/lib/layerindexlib/cooker.py
@@ -0,0 +1,338 @@
+# Copyright (C) 2016-2018 Wind River Systems, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import logging
+import json
+
+from collections import OrderedDict, defaultdict
+
+from urllib.parse import unquote
+
+import layerindexlib
+
+from layerindexlib.common import IndexPlugin
+from layerindexlib.common import LayerIndexError
+from layerindexlib.common import add_element
+
+logger = logging.getLogger('BitBake.layerindexlib.cooker')
+
+import bb.utils
+
+def plugin_init(plugins):
+ return CookerPlugin()
+
+class CookerPlugin(IndexPlugin):
+ def __init__(self):
+ self.type = "cooker"
+ self.server_connection = None
+ self.ui_module = None
+ self.server = None
+
+ def _run_command(self, command, path, default=None):
+ try:
+ result, _ = bb.process.run(command, cwd=path)
+ result = result.strip()
+ except bb.process.ExecutionError:
+ result = default
+ return result
+
+ def _handle_git_remote(self, remote):
+ if "://" not in remote:
+ if ':' in remote:
+ # This is assumed to be ssh
+ remote = "ssh://" + remote
+ else:
+ # This is assumed to be a file path
+ remote = "file://" + remote
+ return remote
+
+ def _get_bitbake_info(self):
+ """Return a tuple of bitbake information"""
+
+ # Our path SHOULD be .../bitbake/lib/layerindex/cooker.py
+ bb_path = os.path.dirname(__file__) # .../bitbake/lib/layerindex/cooker.py
+ bb_path = os.path.dirname(bb_path) # .../bitbake/lib/layerindex
+ bb_path = os.path.dirname(bb_path) # .../bitbake/lib
+ bb_path = os.path.dirname(bb_path) # .../bitbake
+ bb_path = self._run_command('git rev-parse --show-toplevel', os.path.dirname(__file__), default=bb_path)
+ bb_branch = self._run_command('git rev-parse --abbrev-ref HEAD', bb_path, default="<unknown>")
+ bb_rev = self._run_command('git rev-parse HEAD', bb_path, default="<unknown>")
+ for remotes in self._run_command('git remote -v', bb_path, default="").split("\n"):
+ remote = remotes.split("\t")[1].split(" ")[0]
+ if "(fetch)" == remotes.split("\t")[1].split(" ")[1]:
+ bb_remote = self._handle_git_remote(remote)
+ break
+ else:
+ bb_remote = self._handle_git_remote(bb_path)
+
+ return (bb_remote, bb_branch, bb_rev, bb_path)
+
+ def _load_bblayers(self, branches=None):
+ """Load the BBLAYERS and related collection information"""
+
+ d = self.lindex.data
+
+ if not branches:
+ branches = d.getVar('LAYERSERIES_CORENAMES') or "HEAD"
+
+ index = {}
+
+ branchId = 0
+ index['branches'] = {}
+
+ layerItemId = 0
+ index['layerItems'] = {}
+
+ layerBranchId = 0
+ index['layerBranches'] = {}
+
+ bblayers = d.getVar('BBLAYERS').split()
+
+ if not bblayers:
+ # It's blank! Nothing to process...
+ return index
+
+ collections = d.getVar('BBFILE_COLLECTIONS')
+ layerconfs = d.varhistory.get_variable_items_files('BBFILE_COLLECTIONS', d)
+ bbfile_collections = {layer: os.path.dirname(os.path.dirname(path)) for layer, path in layerconfs.items()}
+
+ (_, bb_branch, _, _) = self._get_bitbake_info()
+
+ for branch in branches.split():
+ branchId += 1
+ index['branches'][branchId] = layerindexlib.Branch(index, None)
+ index['branches'][branchId].define_data(branchId, branch, bb_branch)
+
+ for entry in collections.split():
+ layerpath = entry
+ if entry in bbfile_collections:
+ layerpath = bbfile_collections[entry]
+
+ layername = d.getVar('BBLAYERS_LAYERINDEX_NAME_%s' % entry) or os.path.basename(layerpath)
+ layerversion = d.getVar('LAYERVERSION_%s' % entry) or ""
+ layerurl = self._handle_git_remote(layerpath)
+
+ layersubdir = ""
+ layerrev = "<unknown>"
+ layerbranch = "<unknown>"
+
+ if os.path.isdir(layerpath):
+ layerbasepath = self._run_command('git rev-parse --show-toplevel', layerpath, default=layerpath)
+ if os.path.abspath(layerpath) != os.path.abspath(layerbasepath):
+ layersubdir = os.path.abspath(layerpath)[len(layerbasepath) + 1:]
+
+ layerbranch = self._run_command('git rev-parse --abbrev-ref HEAD', layerpath, default="<unknown>")
+ layerrev = self._run_command('git rev-parse HEAD', layerpath, default="<unknown>")
+
+ for remotes in self._run_command('git remote -v', layerpath, default="").split("\n"):
+ remote = remotes.split("\t")[1].split(" ")[0]
+ if "(fetch)" == remotes.split("\t")[1].split(" ")[1]:
+ layerurl = self._handle_git_remote(remote)
+ break
+
+ layerItemId += 1
+ index['layerItems'][layerItemId] = layerindexlib.LayerItem(index, None)
+ index['layerItems'][layerItemId].define_data(layerItemId, layername, description=layerpath, vcs_url=layerurl)
+
+ for branchId in index['branches']:
+ layerBranchId += 1
+ index['layerBranches'][layerBranchId] = layerindexlib.LayerBranch(index, None)
+ index['layerBranches'][layerBranchId].define_data(layerBranchId, entry, layerversion, layerItemId, branchId,
+ vcs_subdir=layersubdir, vcs_last_rev=layerrev, actual_branch=layerbranch)
+
+ return index
+
+
+ def load_index(self, ud, load):
+ """
+ Fetches layer information from a build configuration.
+
+ The return value is a dictionary containing API,
+ layer, branch, dependency, recipe, machine, distro, information.
+
+ ud path is ignored.
+ """
+
+ if ud.type != 'file':
+ raise bb.fetch2.FetchError('%s is not a supported protocol, only file, http and https are support.')
+
+ d = self.lindex.data
+
+ branches = d.getVar('LAYERSERIES_CORENAMES') or "HEAD"
+ if 'branch' in ud.parm:
+ branches = ' '.join(ud.parm['branch'].split(','))
+
+ logger.debug(1, "Loading cooker data branch %s" % branches)
+
+ lindex = self._load_bblayers(branches=branches)
+
+ lindex['CONFIG'] = {}
+ lindex['CONFIG']['TYPE'] = self.type
+ lindex['CONFIG']['URL'] = ud.url
+
+ if 'desc' in ud.parm:
+ lindex['CONFIG']['DESCRIPTION'] = unquote(ud.parm['desc'])
+ else:
+ lindex['CONFIG']['DESCRIPTION'] = ud.path
+
+ if 'cache' in ud.parm:
+ lindex['CONFIG']['CACHE'] = ud.parm['cache']
+
+ if 'branch' in ud.parm:
+ lindex['CONFIG']['BRANCH'] = ud.parm['branch']
+ else:
+ lindex['CONFIG']['BRANCH'] = d.getVar('LAYERSERIES_CORENAMES') or "HEAD"
+
+ # ("layerDependencies", layerindexlib.LayerDependency)
+ layerDependencyId = 0
+ if "layerDependencies" in load.split():
+ lindex['layerDependencies'] = {}
+ for layerBranchId in lindex['layerBranches']:
+ branchName = lindex['layerBranches'][layerBranchId].get_branch().get_name()
+ collection = lindex['layerBranches'][layerBranchId].get_collection()
+
+ def add_dependency(layerDependencyId, lindex, deps, required):
+ try:
+ depDict = bb.utils.explode_dep_versions2(deps)
+ except bb.utils.VersionStringException as vse:
+ bb.fatal('Error parsing LAYERDEPENDS_%s: %s' % (c, str(vse)))
+
+ for dep, oplist in list(depDict.items()):
+ # We need to search ourselves, so use the _ version...
+ depLayerBranch = self.lindex._find_collection(lindex, dep, branch=branchName)
+ if not depLayerBranch:
+ # Missing dependency?!
+ logger.error('Missing dependency %s (%s)' % (dep, branchName))
+ continue
+
+ # We assume that the oplist matches...
+ layerDependencyId += 1
+ layerDependency = layerindexlib.LayerDependency(lindex, None)
+ layerDependency.define_data(id=layerDependencyId,
+ required=required, layerbranch=layerBranchId,
+ dependency=depLayerBranch.get_layer_id())
+
+ logger.debug(1, '%s requires %s' % (layerDependency.get_layer().get_name(), layerDependency.get_dependency_layer().get_name()))
+ lindex = add_element("layerDependencies", [layerDependency], lindex)
+
+ return layerDependencyId
+
+ deps = d.getVar("LAYERDEPENDS_%s" % collection)
+ if deps:
+ layerDependencyId = add_dependency(layerDependencyId, lindex, deps, True)
+
+ deps = d.getVar("LAYERRECOMMENDS_%s" % collection)
+ if deps:
+ layerDependencyId = add_dependency(layerDependencyId, lindex, deps, False)
+
+ # Need to load recipes here (requires cooker access)
+ recipeId = 0
+ ## TODO: NOT IMPLEMENTED
+ # The code following this is an example of what needs to be
+ # implemented. However, it does not work as-is.
+ if False and 'recipes' in load.split():
+ lindex['recipes'] = {}
+
+ ret = self.ui_module.main(self.server_connection.connection, self.server_connection.events, config_params)
+
+ all_versions = self._run_command('allProviders')
+
+ all_versions_list = defaultdict(list, all_versions)
+ for pn in all_versions_list:
+ for ((pe, pv, pr), fpath) in all_versions_list[pn]:
+ realfn = bb.cache.virtualfn2realfn(fpath)
+
+ filepath = os.path.dirname(realfn[0])
+ filename = os.path.basename(realfn[0])
+
+ # This is all HORRIBLY slow, and likely unnecessary
+ #dscon = self._run_command('parseRecipeFile', fpath, False, [])
+ #connector = myDataStoreConnector(self, dscon.dsindex)
+ #recipe_data = bb.data.init()
+ #recipe_data.setVar('_remote_data', connector)
+
+ #summary = recipe_data.getVar('SUMMARY')
+ #description = recipe_data.getVar('DESCRIPTION')
+ #section = recipe_data.getVar('SECTION')
+ #license = recipe_data.getVar('LICENSE')
+ #homepage = recipe_data.getVar('HOMEPAGE')
+ #bugtracker = recipe_data.getVar('BUGTRACKER')
+ #provides = recipe_data.getVar('PROVIDES')
+
+ layer = bb.utils.get_file_layer(realfn[0], self.config_data)
+
+ depBranchId = collection_layerbranch[layer]
+
+ recipeId += 1
+ recipe = layerindexlib.Recipe(lindex, None)
+ recipe.define_data(id=recipeId,
+ filename=filename, filepath=filepath,
+ pn=pn, pv=pv,
+ summary=pn, description=pn, section='?',
+ license='?', homepage='?', bugtracker='?',
+ provides='?', bbclassextend='?', inherits='?',
+ blacklisted='?', layerbranch=depBranchId)
+
+ lindex = addElement("recipes", [recipe], lindex)
+
+ # ("machines", layerindexlib.Machine)
+ machineId = 0
+ if 'machines' in load.split():
+ lindex['machines'] = {}
+
+ for layerBranchId in lindex['layerBranches']:
+ # load_bblayers uses the description to cache the actual path...
+ machine_path = lindex['layerBranches'][layerBranchId].getDescription()
+ machine_path = os.path.join(machine_path, 'conf/machine')
+ if os.path.isdir(machine_path):
+ for (dirpath, _, filenames) in os.walk(machine_path):
+ # Ignore subdirs...
+ if not dirpath.endswith('conf/machine'):
+ continue
+ for fname in filenames:
+ if fname.endswith('.conf'):
+ machineId += 1
+ machine = layerindexlib.Machine(lindex, None)
+ machine.define_data(id=machineId, name=fname[:-5],
+ description=fname[:-5],
+ layerbranch=collection_layerbranch[entry])
+
+ lindex = add_element("machines", [machine], lindex)
+
+ # ("distros", layerindexlib.Distro)
+ distroId = 0
+ if 'distros' in load.split():
+ lindex['distros'] = {}
+
+ for layerBranchId in lindex['layerBranches']:
+ # load_bblayers uses the description to cache the actual path...
+ distro_path = lindex['layerBranches'][layerBranchId].getDescription()
+ distro_path = os.path.join(distro_path, 'conf/distro')
+ if os.path.isdir(distro_path):
+ for (dirpath, _, filenames) in os.walk(distro_path):
+ # Ignore subdirs...
+ if not dirpath.endswith('conf/distro'):
+ continue
+ for fname in filenames:
+ if fname.endswith('.conf'):
+ distroId += 1
+ distro = layerindexlib.Distro(lindex, None)
+ distro.define_data(id=distroId, name=fname[:-5],
+ description=fname[:-5],
+ layerbranch=collection_layerbranch[entry])
+
+ lindex = add_element("distros", [distro], lindex)
+
+ return lindex
diff --git a/lib/layerindexlib/restapi.py b/lib/layerindexlib/restapi.py
new file mode 100644
index 0000000..fde32d2
--- /dev/null
+++ b/lib/layerindexlib/restapi.py
@@ -0,0 +1,375 @@
+# Copyright (C) 2016-2018 Wind River Systems, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import logging
+import bb.fetch2
+import json
+from urllib.parse import unquote
+
+import layerindexlib
+
+from layerindexlib.common import IndexPlugin
+from layerindexlib.common import fetch_url
+from layerindexlib.common import LayerIndexError
+from layerindexlib.common import add_raw_element
+
+logger = logging.getLogger('BitBake.layerindexlib.restapi')
+
+def plugin_init(plugins):
+ return RestApiPlugin()
+
+class RestApiPlugin(IndexPlugin):
+ def __init__(self):
+ self.type = "restapi"
+
+ def load_index(self, ud, load):
+ """
+ Fetches layer information from a local or remote layer index.
+
+ The return value is a dictionary containing API,
+ layer, branch, dependency, recipe, machine, distro, information.
+
+ url is the url to the rest api of the layer index, such as:
+ http://layers.openembedded.org/layerindex/api/
+
+ Or a local file...
+ """
+
+ if ud.type == 'file':
+ return self.load_index_file(ud, load)
+
+ if ud.type == 'http' or ud.type == 'https':
+ return self.load_index_web(ud, load)
+
+ raise bb.fetch2.FetchError('%s is not a supported protocol, only file, http and https are support.')
+
+
+ def load_index_file(self, ud, load):
+ """
+ Fetches layer information from a local file or directory.
+ The return value is a dictionary containing API,
+ layer, branch, dependency, recipe, machine, distro,
+ and template information.
+
+ ud is the parsed url to the local file or directory.
+ """
+ if not os.path.exists(ud.path):
+ raise FileNotFoundError(ud.path)
+
+ lindex = {}
+
+ lindex['CONFIG'] = {}
+ lindex['CONFIG']['TYPE'] = self.type
+ lindex['CONFIG']['URL'] = ud.url
+
+ if 'desc' in ud.parm:
+ lindex['CONFIG']['DESCRIPTION'] = unquote(ud.parm['desc'])
+ else:
+ lindex['CONFIG']['DESCRIPTION'] = ud.path
+
+ if 'cache' in ud.parm:
+ lindex['CONFIG']['CACHE'] = ud.parm['cache']
+
+ branches = None
+ if 'branch' in ud.parm:
+ branches = ud.parm['branch']
+ lindex['CONFIG']['BRANCH'] = branches
+
+
+ def load_cache(path, lindex, branches=None):
+ logger.debug(1, 'Loading json file %s' % path)
+ with open(path, 'rt', encoding='utf-8') as f:
+ pindex = json.load(f)
+
+ # Filter the branches on loaded files...
+ newpBranch = []
+ if branches:
+ for branch in (branches or "").split(','):
+ if 'branches' in pindex:
+ for br in pindex['branches']:
+ if br['name'] == branch:
+ newpBranch.append(br)
+ else:
+ if 'branches' in pindex:
+ newpBranch = pindex['branches']
+
+ if newpBranch:
+ lindex = add_raw_element('branches', layerindexlib.Branch, { 'branches' : newpBranch }, lindex)
+ else:
+ logger.debug(1, 'No matching branchs (%s) in index file(s)' % branches)
+ # No matching branches.. return nothing...
+ return
+
+ for (lName, lType) in [("layerItems", layerindexlib.LayerItem),
+ ("layerBranches", layerindexlib.LayerBranch),
+ ("layerDependencies", layerindexlib.LayerDependency),
+ ("recipes", layerindexlib.Recipe),
+ ("machines", layerindexlib.Machine),
+ ("distros", layerindexlib.Distro)]:
+ if lName in pindex:
+ lindex = add_raw_element(lName, lType, pindex, lindex)
+
+
+ if not os.path.isdir(ud.path):
+ load_cache(ud.path, lindex, branches)
+ return lindex
+
+ logger.debug(1, 'Loading from dir %s...' % (ud.path))
+ for (dirpath, _, filenames) in os.walk(ud.path):
+ for filename in filenames:
+ if not filename.endswith('.json'):
+ continue
+ fpath = os.path.join(dirpath, filename)
+ load_cache(fpath, lindex, branches)
+
+ return lindex
+
+
+ def load_index_web(self, ud, load):
+ """
+ Fetches layer information from a remote layer index.
+ The return value is a dictionary containing API,
+ layer, branch, dependency, recipe, machine, distro,
+ and template information.
+
+ ud is the parsed url to the rest api of the layer index, such as:
+ http://layers.openembedded.org/layerindex/api/
+ """
+
+ def _get_json_response(apiurl=None, username=None, password=None, retry=True):
+ assert apiurl is not None
+
+ logger.debug(1, "fetching %s" % apiurl)
+
+ res = fetch_url(apiurl, username=username, password=password)
+
+ try:
+ parsed = json.loads(res.read().decode('utf-8'))
+ except ConnectionResetError:
+ if retry:
+ logger.debug(1, "%s: Connection reset by peer. Retrying..." % url)
+ parsed = _get_json_response(apiurl=apiurl, username=username, password=password, retry=False)
+ logger.debug(1, "%s: retry successful.")
+ else:
+ raise bb.fetch2.FetchError('%s: Connection reset by peer. Is there a firewall blocking your connection?' % apiurl)
+
+ return parsed
+
+ lindex = {}
+
+ lindex['CONFIG'] = {}
+ lindex['CONFIG']['TYPE'] = self.type
+ lindex['CONFIG']['URL'] = ud.url
+
+ if 'desc' in ud.parm:
+ lindex['CONFIG']['DESCRIPTION'] = unquote(ud.parm['desc'])
+ else:
+ lindex['CONFIG']['DESCRIPTION'] = ud.host
+
+ if 'cache' in ud.parm:
+ lindex['CONFIG']['CACHE'] = ud.parm['cache']
+
+ if 'branch' in ud.parm:
+ lindex['CONFIG']['BRANCH'] = ud.parm['branch']
+
+ try:
+ lindex['apilinks'] = _get_json_response(bb.fetch2.encodeurl( (ud.type, ud.host, ud.path, None, None, None) ),
+ username=ud.user, password=ud.pswd)
+ except Exception as e:
+ raise LayerIndexError("Unable to load layer index %s: %s" % (ud.url, e))
+
+ branches = None
+ if 'branch' in ud.parm and ud.parm['branch']:
+ branches = ud.parm['branch']
+
+
+ # Local raw index set...
+ pindex = {}
+
+ # Load the branches element
+ filter = ""
+ if branches:
+ filter = "?filter=name:%s" % branches
+
+ logger.debug(1, "Loading %s from %s" % ('branches', lindex['apilinks']['branches']))
+ pindex['branches'] = _get_json_response(lindex['apilinks']['branches'] + filter,
+ username=ud.user, password=ud.pswd)
+ if not pindex['branches']:
+ logger.debug(1, "No valid branches (%s) found at url %s." % (branches or "*", ud.url))
+ return lindex
+ lindex = add_raw_element("branches", layerindexlib.Branch, pindex, lindex)
+
+
+ # Load all of the layerItems (these can not be easily filtered)
+ logger.debug(1, "Loading %s from %s" % ('layerItems', lindex['apilinks']['layerItems']))
+ pindex['layerItems'] = _get_json_response(lindex['apilinks']['layerItems'],
+ username=ud.user, password=ud.pswd)
+ if not pindex['layerItems']:
+ logger.debug(1, "No layers were found at url %s." % (ud.url))
+ return lindex
+ lindex = add_raw_element("layerItems", layerindexlib.LayerItem, pindex, lindex)
+
+
+ # From this point on load the contents for each branch. Otherwise we
+ # could run into a timeout.
+ for branch in lindex['branches']:
+ filter = "?filter=branch__name:%s" % lindex['branches'][branch].get_name()
+
+ logger.debug(1, "Loading %s from %s" % ('layerBranches', lindex['apilinks']['layerBranches']))
+ pindex['layerBranches'] = _get_json_response(lindex['apilinks']['layerBranches'] + filter,
+ username=ud.user, password=ud.pswd)
+ if not pindex['layerBranches']:
+ logger.debug(1, "No valid layer branches (%s) found at url %s." % (branches or "*", ud.url))
+ return lindex
+ lindex = add_raw_element("layerBranches", layerindexlib.LayerBranch, pindex, lindex)
+
+
+ # Load the rest, they all have a similar format
+ filter = "?filter=layerbranch__branch__name:%s" % lindex['branches'][branch].get_name()
+ for (lName, lType) in [("layerDependencies", layerindexlib.LayerDependency),
+ ("recipes", layerindexlib.Recipe),
+ ("machines", layerindexlib.Machine),
+ ("distros", layerindexlib.Distro)]:
+ if lName not in load.split():
+ continue
+ logger.debug(1, "Loading %s from %s" % (lName, lindex['apilinks'][lName]))
+ pindex[lName] = _get_json_response(lindex['apilinks'][lName] + filter,
+ username=ud.user, password=ud.pswd)
+ lindex = add_raw_element(lName, lType, pindex, lindex)
+
+
+ return lindex
+
+ def store_index(self, ud, lindex):
+ """
+ Store layer information into a local file/dir.
+
+ The return value is a dictionary containing API,
+ layer, branch, dependency, recipe, machine, distro, information.
+
+ ud is a parsed url to a directory or file. If the path is a
+ directory, we will split the files into one file per layer.
+ If the path is to a file (exists or not) the entire DB will be
+ dumped into that one file.
+ """
+
+ if ud.type != 'file':
+ raise NotImplementedError('Writing to anything but a file url is not implemented: %s' % ud.url)
+
+ try:
+ layerBranches = lindex['layerBranches']
+ except KeyError:
+ logger.error('No layerBranches to write.')
+ return
+
+
+ def filter_item(layerBranchId, objects):
+ filtered = []
+ for obj in lindex[objects]:
+ try:
+ if lindex[objects][obj].get_layerbranch_id() == layerBranchId:
+ filtered.append(lindex[objects][obj].data)
+ except AttributeError:
+ logger.debug(1, 'No obj.get_layerbranch_id(): %s' % objects)
+ # No simple filter method, just include it...
+ try:
+ filtered.append(lindex[objects][obj].data)
+ except AttributeError:
+ logger.debug(1, 'No obj.data: %s %s' % (objects, type(obj)))
+ filtered.append(obj)
+ return filtered
+
+
+ # Write out to a single file.
+ # Filter out unnecessary items, then sort as we write for determinism
+ if not os.path.isdir(ud.path):
+ pindex = {}
+
+ pindex['branches'] = []
+ pindex['layerItems'] = []
+ pindex['layerBranches'] = []
+
+ for layerBranchId in layerBranches:
+ if layerBranches[layerBranchId].get_branch().data not in pindex['branches']:
+ pindex['branches'].append(layerBranches[layerBranchId].get_branch().data)
+
+ if layerBranches[layerBranchId].get_layer().data not in pindex['layerItems']:
+ pindex['layerItems'].append(layerBranches[layerBranchId].get_layer().data)
+
+ if layerBranches[layerBranchId].data not in pindex['layerBranches']:
+ pindex['layerBranches'].append(layerBranches[layerBranchId].data)
+
+ for entry in lindex:
+ # Skip local items, apilinks and items already processed
+ if entry in lindex['CONFIG']['local'] or \
+ entry == 'apilinks' or \
+ entry == 'branches' or \
+ entry == 'layerBranches' or \
+ entry == 'layerItems':
+ continue
+ if entry not in pindex:
+ pindex[entry] = []
+ pindex[entry].extend(filter_item(layerBranchId, entry))
+
+ bb.debug(1, 'Writing index to %s' % ud.path)
+ with open(ud.path, 'wt') as f:
+ json.dump(layerindexlib.sort_entry(pindex), f, indent=4)
+ return
+
+
+ # Write out to a directory one file per layerBranch
+ # Prepare all layer related items, to create a minimal file.
+ # We have to sort the entries as we write so they are deterministic
+ for layerBranchId in layerBranches:
+ pindex = {}
+
+ for entry in lindex:
+ # Skip local items, apilinks and items already processed
+ if entry in lindex['CONFIG']['local'] or \
+ entry == 'apilinks' or \
+ entry == 'branches' or \
+ entry == 'layerBranches' or \
+ entry == 'layerItems':
+ continue
+ pindex[entry] = filter_item(layerBranchId, entry)
+
+ # Add the layer we're processing as the first one...
+ pindex['branches'] = [layerBranches[layerBranchId].get_branch().data]
+ pindex['layerItems'] = [layerBranches[layerBranchId].get_layer().data]
+ pindex['layerBranches'] = [layerBranches[layerBranchId].data]
+
+ # We also need to include the layerbranch for any dependencies...
+ for layerDep in pindex['layerDependencies']:
+ layerDependency = layerindexlib.LayerDependency(lindex, layerDep)
+
+ layerItem = layerDependency.get_dependency_layer()
+ layerBranch = layerDependency.get_dependency_layerBranch()
+
+ # We need to avoid duplicates...
+ if layerItem.data not in pindex['layerItems']:
+ pindex['layerItems'].append(layerItem.data)
+
+ if layerBranch.data not in pindex['layerBranches']:
+ pindex['layerBranches'].append(layerBranch.data)
+
+ # apply mirroring adjustments here....
+
+ fname = lindex['CONFIG']['DESCRIPTION'] + '__' + pindex['branches'][0]['name'] + '__' + pindex['layerItems'][0]['name']
+ fname = fname.translate(str.maketrans('/ ', '__'))
+ fpath = os.path.join(ud.path, fname)
+
+ bb.debug(1, 'Writing index to %s' % fpath + '.json')
+ with open(fpath + '.json', 'wt') as f:
+ json.dump(layerindexlib.sort_entry(pindex), f, indent=4)
diff --git a/lib/layerindexlib/tests/__init__.py b/lib/layerindexlib/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/lib/layerindexlib/tests/common.py b/lib/layerindexlib/tests/common.py
new file mode 100644
index 0000000..f73bf3d
--- /dev/null
+++ b/lib/layerindexlib/tests/common.py
@@ -0,0 +1,37 @@
+# Copyright (C) 2017-2018 Wind River Systems, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import unittest
+import tempfile
+import os
+import bb
+
+import logging
+
+class LayersTest(unittest.TestCase):
+
+ def setUp(self):
+ self.origdir = os.getcwd()
+ self.d = bb.data.init()
+ self.tempdir = tempfile.mkdtemp()
+ self.logger = logging.getLogger("BitBake")
+
+ def tearDown(self):
+ os.chdir(self.origdir)
+ if os.environ.get("BB_TMPDIR_NOCLEAN") == "yes":
+ print("Not cleaning up %s. Please remove manually." % self.tempdir)
+ else:
+ bb.utils.prunedir(self.tempdir)
+
diff --git a/lib/layerindexlib/tests/cooker.py b/lib/layerindexlib/tests/cooker.py
new file mode 100644
index 0000000..b790732
--- /dev/null
+++ b/lib/layerindexlib/tests/cooker.py
@@ -0,0 +1,125 @@
+# Copyright (C) 2018 Wind River Systems, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import unittest
+import tempfile
+import os
+import bb
+
+import layerindexlib
+from layerindexlib.tests.common import LayersTest
+
+import logging
+
+class LayerIndexCookerTest(LayersTest):
+
+ def setUp(self):
+ LayersTest.setUp(self)
+
+ # Note this is NOT a comprehensive test of cooker, as we can't easily
+ # configure the test data. But we can emulate the basics of the layer.conf
+ # files, so that is what we will do.
+
+ new_topdir = os.path.join(os.path.dirname(__file__), "testdata")
+ new_bbpath = os.path.join(new_topdir, "build")
+
+ self.d.setVar('TOPDIR', new_topdir)
+ self.d.setVar('BBPATH', new_bbpath)
+
+ self.d = bb.parse.handle("%s/conf/bblayers.conf" % new_bbpath, self.d, True)
+ for layer in self.d.getVar('BBLAYERS').split():
+ self.d = bb.parse.handle("%s/conf/layer.conf" % layer, self.d, True)
+
+ self.lindex = layerindexlib.LayerIndex(self.d)
+ self.lindex.load_layerindex('file://%s/;type=cooker' % self.tempdir, load='layerDependencies')
+
+ def test_layerindex_is_empty(self):
+ self.assertFalse(self.lindex.is_empty())
+
+ def test_dependency_resolution(self):
+ # Verify depth first searching...
+ (dependencies, invalidnames) = self.lindex.get_dependencies(names='meta-python')
+
+ first = True
+ for deplayerbranch in dependencies:
+ layerBranch = dependencies[deplayerbranch][0]
+ layerDeps = dependencies[deplayerbranch][1:]
+
+ if not first:
+ continue
+
+ first = False
+
+ # Top of the deps should be openembedded-core, since everything depends on it.
+ self.assertEqual(layerBranch.get_layer().get_name(), "openembedded-core")
+
+ # meta-python should cause an openembedded-core dependency, if not assert!
+ for dep in layerDeps:
+ if dep.get_layer().get_name() == 'meta-python':
+ break
+ else:
+ self.logger.debug(1, "meta-python was not found")
+ self.assetTrue(False)
+
+ # Only check the first element...
+ break
+ else:
+ if first:
+ # Empty list, this is bad.
+ self.logger.debug(1, "Empty list of dependencies")
+ self.assertTrue(False)
+
+ # Last dep should be the requested item
+ layerBranch = dependencies[deplayerbranch][0]
+ self.assertEqual(layerBranch.get_layer().get_name(), "meta-python")
+
+ def test_find_collection(self):
+ def _check(collection, expected):
+ self.logger.debug(1, "Looking for collection %s..." % collection)
+ result = self.lindex.find_collection(collection)
+ if expected:
+ self.assertIsNotNone(result, msg="Did not find %s when it shouldn't be there" % collection)
+ else:
+ self.assertIsNone(result, msg="Found %s when it should be there" % collection)
+
+ tests = [ ('core', True),
+ ('openembedded-core', False),
+ ('networking-layer', True),
+ ('meta-python', True),
+ ('openembedded-layer', True),
+ ('notpresent', False) ]
+
+ for collection,result in tests:
+ _check(collection, result)
+
+ def test_get_layerbranch(self):
+ def _check(name, expected):
+ self.logger.debug(1, "Looking for layerbranch %s..." % name)
+ result = self.lindex.get_layerbranch(name)
+ if expected:
+ self.assertIsNotNone(result, msg="Did not find %s when it shouldn't be there" % collection)
+ else:
+ self.assertIsNone(result, msg="Found %s when it should be there" % collection)
+
+ tests = [ ('openembedded-core', True),
+ ('core', False),
+ ('networking-layer', True),
+ ('meta-python', True),
+ ('openembedded-layer', True),
+ ('notpresent', False) ]
+
+ for collection,result in tests:
+ _check(collection, result)
+
diff --git a/lib/layerindexlib/tests/layerindex.py b/lib/layerindexlib/tests/layerindex.py
new file mode 100644
index 0000000..0fde894
--- /dev/null
+++ b/lib/layerindexlib/tests/layerindex.py
@@ -0,0 +1,233 @@
+# Copyright (C) 2017-2018 Wind River Systems, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import unittest
+import tempfile
+import os
+import bb
+
+from layerindexlib.tests.common import LayersTest
+
+import logging
+
+class LayerObjectTest(LayersTest):
+ def setUp(self):
+ from layerindexlib import Branch, LayerItem, LayerBranch, LayerDependency, Recipe, Machine, Distro
+
+ LayersTest.setUp(self)
+
+ self.index = {}
+
+ branchId = 0
+ layerItemId = 0
+ layerBranchId = 0
+ layerDependencyId = 0
+ recipeId = 0
+ machineId = 0
+ distroId = 0
+
+ self.index['branches'] = {}
+ self.index['layerItems'] = {}
+ self.index['layerBranches'] = {}
+ self.index['layerDependencies'] = {}
+ self.index['recipes'] = {}
+ self.index['machines'] = {}
+ self.index['distros'] = {}
+
+ branchId += 1
+ self.index['branches'][branchId] = Branch(self.index, None)
+ self.index['branches'][branchId].define_data(branchId,
+ 'test_branch', 'bb_test_branch')
+
+ layerItemId +=1
+ self.index['layerItems'][layerItemId] = LayerItem(self.index, None)
+ self.index['layerItems'][layerItemId].define_data(layerItemId, 'test_layerItem',
+ vcs_url='git://git_test_url/test_layerItem')
+
+ layerBranchId +=1
+ self.index['layerBranches'][layerBranchId] = LayerBranch(self.index, None)
+ self.index['layerBranches'][layerBranchId].define_data(layerBranchId,
+ 'test_collection', '99', layerItemId,
+ branchId)
+
+ recipeId += 1
+ self.index['recipes'][recipeId] = Recipe(self.index, None)
+ self.index['recipes'][recipeId].define_data(recipeId, 'test_git.bb',
+ 'recipes-test', 'test', 'git',
+ layerBranchId)
+
+ machineId += 1
+ self.index['machines'][machineId] = Machine(self.index, None)
+ self.index['machines'][machineId].define_data(machineId,
+ 'test_machine', 'test_machine',
+ layerBranchId)
+
+ distroId += 1
+ self.index['distros'][distroId] = Distro(self.index, None)
+ self.index['distros'][distroId].define_data(distroId,
+ 'test_distro', 'test_distro',
+ layerBranchId)
+
+ layerItemId +=1
+ self.index['layerItems'][layerItemId] = LayerItem(self.index, None)
+ self.index['layerItems'][layerItemId].define_data(layerItemId, 'test_layerItem 2',
+ vcs_url='git://git_test_url/test_layerItem')
+
+ layerBranchId +=1
+ self.index['layerBranches'][layerBranchId] = LayerBranch(self.index, None)
+ self.index['layerBranches'][layerBranchId].define_data(layerBranchId,
+ 'test_collection_2', '72', layerItemId,
+ branchId, actual_branch='some_other_branch')
+
+ layerDependencyId += 1
+ self.index['layerDependencies'][layerDependencyId] = LayerDependency(self.index, None)
+ self.index['layerDependencies'][layerDependencyId].define_data(layerDependencyId,
+ layerBranchId, 1)
+
+ layerDependencyId += 1
+ self.index['layerDependencies'][layerDependencyId] = LayerDependency(self.index, None)
+ self.index['layerDependencies'][layerDependencyId].define_data(layerDependencyId,
+ layerBranchId, 1, required=False)
+
+ def test_branch(self):
+ branch = self.index['branches'][1]
+ self.assertEqual(branch.get_id(), 1)
+ self.assertEqual(branch.get_name(), 'test_branch')
+ self.assertEqual(branch.get_short_description(), 'test_branch')
+ self.assertEqual(branch.get_bitbake_branch(), 'bb_test_branch')
+
+ def test_layerItem(self):
+ layerItem = self.index['layerItems'][1]
+ self.assertEqual(layerItem.get_id(), 1)
+ self.assertEqual(layerItem.get_name(), 'test_layerItem')
+ self.assertEqual(layerItem.get_summary(), 'test_layerItem')
+ self.assertEqual(layerItem.get_description(), 'test_layerItem')
+ self.assertEqual(layerItem.get_vcs_url(), 'git://git_test_url/test_layerItem')
+ self.assertEqual(layerItem.get_vcs_web_url(), None)
+ self.assertIsNone(layerItem.get_vcs_web_tree_base_url())
+ self.assertIsNone(layerItem.get_vcs_web_file_base_url())
+ self.assertIsNotNone(layerItem.get_updated())
+
+ layerItem = self.index['layerItems'][2]
+ self.assertEqual(layerItem.get_id(), 2)
+ self.assertEqual(layerItem.get_name(), 'test_layerItem 2')
+ self.assertEqual(layerItem.get_summary(), 'test_layerItem 2')
+ self.assertEqual(layerItem.get_description(), 'test_layerItem 2')
+ self.assertEqual(layerItem.get_vcs_url(), 'git://git_test_url/test_layerItem')
+ self.assertIsNone(layerItem.get_vcs_web_url())
+ self.assertIsNone(layerItem.get_vcs_web_tree_base_url())
+ self.assertIsNone(layerItem.get_vcs_web_file_base_url())
+ self.assertIsNotNone(layerItem.get_updated())
+
+ def test_layerBranch(self):
+ layerBranch = self.index['layerBranches'][1]
+ self.assertEqual(layerBranch.get_id(), 1)
+ self.assertEqual(layerBranch.get_collection(), 'test_collection')
+ self.assertEqual(layerBranch.get_version(), '99')
+ self.assertEqual(layerBranch.get_vcs_subdir(), '')
+ self.assertEqual(layerBranch.get_actual_branch(), 'test_branch')
+ self.assertIsNotNone(layerBranch.get_updated())
+ self.assertEqual(layerBranch.get_layer_id(), 1)
+ self.assertEqual(layerBranch.get_branch_id(), 1)
+ self.assertEqual(layerBranch.get_layer(), self.index['layerItems'][1])
+ self.assertEqual(layerBranch.get_branch(), self.index['branches'][1])
+
+ layerBranch = self.index['layerBranches'][2]
+ self.assertEqual(layerBranch.get_id(), 2)
+ self.assertEqual(layerBranch.get_collection(), 'test_collection_2')
+ self.assertEqual(layerBranch.get_version(), '72')
+ self.assertEqual(layerBranch.get_vcs_subdir(), '')
+ self.assertEqual(layerBranch.get_actual_branch(), 'some_other_branch')
+ self.assertIsNotNone(layerBranch.get_updated())
+ self.assertEqual(layerBranch.get_layer_id(), 2)
+ self.assertEqual(layerBranch.get_branch_id(), 1)
+ self.assertEqual(layerBranch.get_layer(), self.index['layerItems'][2])
+ self.assertEqual(layerBranch.get_branch(), self.index['branches'][1])
+
+ def test_layerDependency(self):
+ layerDependency = self.index['layerDependencies'][1]
+ self.assertEqual(layerDependency.get_id(), 1)
+ self.assertEqual(layerDependency.get_layerbranch_id(), 2)
+ self.assertEqual(layerDependency.get_layerbranch(), self.index['layerBranches'][2])
+ self.assertEqual(layerDependency.get_layer_id(), 2)
+ self.assertEqual(layerDependency.get_layer(), self.index['layerItems'][2])
+ self.assertTrue(layerDependency.is_required())
+ self.assertEqual(layerDependency.get_dependency_id(), 1)
+ self.assertEqual(layerDependency.get_dependency_layer(), self.index['layerItems'][1])
+ self.assertEqual(layerDependency.get_dependency_layerBranch(), self.index['layerBranches'][1])
+
+ # Previous check used the fall back method.. now use the faster method
+ # Create quick lookup layerBranches_layerId_branchId table
+ if 'layerBranches' in self.index:
+ # Create associated quick lookup indexes
+ self.index['layerBranches_layerId_branchId'] = {}
+ for layerBranchId in self.index['layerBranches']:
+ obj = self.index['layerBranches'][layerBranchId]
+ self.index['layerBranches_layerId_branchId']["%s:%s" % (obj.get_layer_id(), obj.get_branch_id())] = obj
+
+ layerDependency = self.index['layerDependencies'][2]
+ self.assertEqual(layerDependency.get_id(), 2)
+ self.assertEqual(layerDependency.get_layerbranch_id(), 2)
+ self.assertEqual(layerDependency.get_layerbranch(), self.index['layerBranches'][2])
+ self.assertEqual(layerDependency.get_layer_id(), 2)
+ self.assertEqual(layerDependency.get_layer(), self.index['layerItems'][2])
+ self.assertFalse(layerDependency.is_required())
+ self.assertEqual(layerDependency.get_dependency_id(), 1)
+ self.assertEqual(layerDependency.get_dependency_layer(), self.index['layerItems'][1])
+ self.assertEqual(layerDependency.get_dependency_layerBranch(), self.index['layerBranches'][1])
+
+ def test_recipe(self):
+ recipe = self.index['recipes'][1]
+ self.assertEqual(recipe.get_id(), 1)
+ self.assertEqual(recipe.get_layerbranch_id(), 1)
+ self.assertEqual(recipe.get_layerbranch(), self.index['layerBranches'][1])
+ self.assertEqual(recipe.get_layer_id(), 1)
+ self.assertEqual(recipe.get_layer(), self.index['layerItems'][1])
+ self.assertEqual(recipe.get_filename(), 'test_git.bb')
+ self.assertEqual(recipe.get_filepath(), 'recipes-test')
+ self.assertEqual(recipe.get_fullpath(), 'recipes-test/test_git.bb')
+ self.assertEqual(recipe.get_summary(), "")
+ self.assertEqual(recipe.get_description(), "")
+ self.assertEqual(recipe.get_section(), "")
+ self.assertEqual(recipe.get_pn(), 'test')
+ self.assertEqual(recipe.get_pv(), 'git')
+ self.assertEqual(recipe.get_license(), "")
+ self.assertEqual(recipe.get_homepage(), "")
+ self.assertEqual(recipe.get_bugtracker(), "")
+ self.assertEqual(recipe.get_provides(), "")
+ self.assertIsNotNone(recipe.get_updated())
+ self.assertEqual(recipe.get_inherits(), "")
+
+ def test_machine(self):
+ machine = self.index['machines'][1]
+ self.assertEqual(machine.get_id(), 1)
+ self.assertEqual(machine.get_layerbranch_id(), 1)
+ self.assertEqual(machine.get_layerbranch(), self.index['layerBranches'][1])
+ self.assertEqual(machine.get_layer_id(), 1)
+ self.assertEqual(machine.get_layer(), self.index['layerItems'][1])
+ self.assertEqual(machine.get_name(), 'test_machine')
+ self.assertEqual(machine.get_description(), 'test_machine')
+ self.assertIsNotNone(machine.get_updated())
+
+ def test_distro(self):
+ distro = self.index['distros'][1]
+ self.assertEqual(distro.get_id(), 1)
+ self.assertEqual(distro.get_layerbranch_id(), 1)
+ self.assertEqual(distro.get_layerbranch(), self.index['layerBranches'][1])
+ self.assertEqual(distro.get_layer_id(), 1)
+ self.assertEqual(distro.get_layer(), self.index['layerItems'][1])
+ self.assertEqual(distro.get_name(), 'test_distro')
+ self.assertEqual(distro.get_description(), 'test_distro')
+ self.assertIsNotNone(distro.get_updated())
diff --git a/lib/layerindexlib/tests/restapi.py b/lib/layerindexlib/tests/restapi.py
new file mode 100644
index 0000000..9d5bedb
--- /dev/null
+++ b/lib/layerindexlib/tests/restapi.py
@@ -0,0 +1,170 @@
+# Copyright (C) 2017-2018 Wind River Systems, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import unittest
+import tempfile
+import os
+import bb
+
+import layerindexlib
+from layerindexlib.tests.common import LayersTest
+
+import logging
+
+class LayerIndexWebRestApiTest(LayersTest):
+
+ if os.environ.get("BB_SKIP_NETTESTS") == "yes":
+ print("Unset BB_SKIP_NETTESTS to run network tests")
+ else:
+ def setUp(self):
+ LayersTest.setUp(self)
+ self.lindex = layerindexlib.LayerIndex(self.d)
+ self.lindex.load_layerindex('http://layers.openembedded.org/layerindex/api/;type=restapi;branch=sumo', load='layerDependencies')
+
+ def test_layerindex_is_empty(self):
+ self.assertFalse(self.lindex.is_empty())
+
+ def test_layerindex_store_file(self):
+ self.lindex.store_layerindex('file://%s/file.json;type=restapi' % self.tempdir, self.lindex.lindex[0])
+
+ self.assertTrue(os.path.isfile('%s/file.json' % self.tempdir))
+
+ reload = layerindexlib.LayerIndex(self.d)
+ reload.load_layerindex('file://%s/file.json;type=restapi' % self.tempdir)
+
+ self.assertFalse(reload.is_empty())
+
+ # Calculate layerItems in original index that should NOT be in reload
+ layerItemNames = []
+ for itemId in self.lindex.lindex[0]['layerItems']:
+ layerItemNames.append(self.lindex.lindex[0]['layerItems'][itemId].get_name())
+
+ for layerBranchId in self.lindex.lindex[0]['layerBranches']:
+ layerItemNames.remove(self.lindex.lindex[0]['layerBranches'][layerBranchId].get_layer().get_name())
+
+ for itemId in reload.lindex[0]['layerItems']:
+ self.assertFalse(reload.lindex[0]['layerItems'][itemId].get_name() in layerItemNames)
+
+ # Compare the original to what we wrote...
+ for type in self.lindex.lindex[0]:
+ if type == 'apilinks' or \
+ type == 'layerItems' or \
+ type in self.lindex.lindex[0]['CONFIG']['local']:
+ continue
+ for id in self.lindex.lindex[0][type]:
+ self.logger.debug(1, "type %s" % (type))
+
+ self.assertTrue(id in reload.lindex[0][type])
+
+ self.logger.debug(1, "%s ? %s" % (self.lindex.lindex[0][type][id], reload.lindex[0][type][id]))
+
+ self.assertEqual(self.lindex.lindex[0][type][id], reload.lindex[0][type][id])
+
+ def test_layerindex_store_split(self):
+ self.lindex.store_layerindex('file://%s;type=restapi' % self.tempdir, self.lindex.lindex[0])
+
+ reload = layerindexlib.LayerIndex(self.d)
+ reload.load_layerindex('file://%s;type=restapi' % self.tempdir)
+
+ self.assertFalse(reload.is_empty())
+
+ for type in self.lindex.lindex[0]:
+ if type == 'apilinks' or \
+ type == 'layerItems' or \
+ type in self.lindex.lindex[0]['CONFIG']['local']:
+ continue
+ for id in self.lindex.lindex[0][type]:
+ self.logger.debug(1, "type %s" % (type))
+
+ self.assertTrue(id in reload.lindex[0][type])
+
+ self.logger.debug(1, "%s ? %s" % (self.lindex.lindex[0][type][id], reload.lindex[0][type][id]))
+
+ self.assertEqual(self.lindex.lindex[0][type][id], reload.lindex[0][type][id])
+
+ def test_dependency_resolution(self):
+ # Verify depth first searching...
+ (dependencies, invalidnames) = self.lindex.get_dependencies(names='meta-python')
+
+ first = True
+ for deplayerbranch in dependencies:
+ layerBranch = dependencies[deplayerbranch][0]
+ layerDeps = dependencies[deplayerbranch][1:]
+
+ if not first:
+ continue
+
+ first = False
+
+ # Top of the deps should be openembedded-core, since everything depends on it.
+ self.assertEqual(layerBranch.get_layer().get_name(), "openembedded-core")
+
+ # meta-python should cause an openembedded-core dependency, if not assert!
+ for dep in layerDeps:
+ if dep.get_layer().get_name() == 'meta-python':
+ break
+ else:
+ self.logger.debug(1, "meta-python was not found")
+ self.assetTrue(False)
+
+ # Only check the first element...
+ break
+ else:
+ # Empty list, this is bad.
+ self.logger.debug(1, "Empty list of dependencies")
+ self.assertIsNotNone(first, msg="Empty list of dependencies")
+
+ # Last dep should be the requested item
+ layerBranch = dependencies[deplayerbranch][0]
+ self.assertEqual(layerBranch.get_layer().get_name(), "meta-python")
+
+ def test_find_collection(self):
+ def _check(collection, expected):
+ self.logger.debug(1, "Looking for collection %s..." % collection)
+ result = self.lindex.find_collection(collection)
+ if expected:
+ self.assertIsNotNone(result, msg="Did not find %s when it shouldn't be there" % collection)
+ else:
+ self.assertIsNone(result, msg="Found %s when it should be there" % collection)
+
+ tests = [ ('core', True),
+ ('openembedded-core', False),
+ ('networking-layer', True),
+ ('meta-python', True),
+ ('openembedded-layer', True),
+ ('notpresent', False) ]
+
+ for collection,result in tests:
+ _check(collection, result)
+
+ def test_get_layerbranch(self):
+ def _check(name, expected):
+ self.logger.debug(1, "Looking for layerbranch %s..." % name)
+ result = self.lindex.get_layerbranch(name)
+ if expected:
+ self.assertIsNotNone(result, msg="Did not find %s when it shouldn't be there" % collection)
+ else:
+ self.assertIsNone(result, msg="Found %s when it should be there" % collection)
+
+ tests = [ ('openembedded-core', True),
+ ('core', False),
+ ('meta-networking', True),
+ ('meta-python', True),
+ ('meta-oe', True),
+ ('notpresent', False) ]
+
+ for collection,result in tests:
+ _check(collection, result)
+
diff --git a/lib/layerindexlib/tests/testdata/README b/lib/layerindexlib/tests/testdata/README
new file mode 100644
index 0000000..36ab40b
--- /dev/null
+++ b/lib/layerindexlib/tests/testdata/README
@@ -0,0 +1,11 @@
+This test data is used to verify the 'cooker' module of the layerindex.
+
+The module consists of a faux project bblayers.conf with four layers defined.
+
+layer1 - openembedded-core
+layer2 - networking-layer
+layer3 - meta-python
+layer4 - openembedded-layer (meta-oe)
+
+Since we do not have a fully populated cooker, we use this to test the
+basic index generation, and not any deep recipe based contents.
diff --git a/lib/layerindexlib/tests/testdata/build/conf/bblayers.conf b/lib/layerindexlib/tests/testdata/build/conf/bblayers.conf
new file mode 100644
index 0000000..40429b2
--- /dev/null
+++ b/lib/layerindexlib/tests/testdata/build/conf/bblayers.conf
@@ -0,0 +1,15 @@
+LAYERSERIES_CORENAMES = "sumo"
+
+# LAYER_CONF_VERSION is increased each time build/conf/bblayers.conf
+# changes incompatibly
+LCONF_VERSION = "7"
+
+BBPATH = "${TOPDIR}"
+BBFILES ?= ""
+
+BBLAYERS ?= " \
+ ${TOPDIR}/layer1 \
+ ${TOPDIR}/layer2 \
+ ${TOPDIR}/layer3 \
+ ${TOPDIR}/layer4 \
+ "
diff --git a/lib/layerindexlib/tests/testdata/layer1/conf/layer.conf b/lib/layerindexlib/tests/testdata/layer1/conf/layer.conf
new file mode 100644
index 0000000..966d531
--- /dev/null
+++ b/lib/layerindexlib/tests/testdata/layer1/conf/layer.conf
@@ -0,0 +1,17 @@
+# We have a conf and classes directory, add to BBPATH
+BBPATH .= ":${LAYERDIR}"
+# We have recipes-* directories, add to BBFILES
+BBFILES += "${LAYERDIR}/recipes-*/*/*.bb"
+
+BBFILE_COLLECTIONS += "core"
+BBFILE_PATTERN_core = "^${LAYERDIR}/"
+BBFILE_PRIORITY_core = "5"
+
+LAYERSERIES_CORENAMES = "sumo"
+
+# This should only be incremented on significant changes that will
+# cause compatibility issues with other layers
+LAYERVERSION_core = "11"
+LAYERSERIES_COMPAT_core = "sumo"
+
+BBLAYERS_LAYERINDEX_NAME_core = "openembedded-core"
diff --git a/lib/layerindexlib/tests/testdata/layer2/conf/layer.conf b/lib/layerindexlib/tests/testdata/layer2/conf/layer.conf
new file mode 100644
index 0000000..7569d1c
--- /dev/null
+++ b/lib/layerindexlib/tests/testdata/layer2/conf/layer.conf
@@ -0,0 +1,20 @@
+# We have a conf and classes directory, add to BBPATH
+BBPATH .= ":${LAYERDIR}"
+
+# We have a packages directory, add to BBFILES
+BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \
+ ${LAYERDIR}/recipes-*/*/*.bbappend"
+
+BBFILE_COLLECTIONS += "networking-layer"
+BBFILE_PATTERN_networking-layer := "^${LAYERDIR}/"
+BBFILE_PRIORITY_networking-layer = "5"
+
+# This should only be incremented on significant changes that will
+# cause compatibility issues with other layers
+LAYERVERSION_networking-layer = "1"
+
+LAYERDEPENDS_networking-layer = "core"
+LAYERDEPENDS_networking-layer += "openembedded-layer"
+LAYERDEPENDS_networking-layer += "meta-python"
+
+LAYERSERIES_COMPAT_networking-layer = "sumo"
diff --git a/lib/layerindexlib/tests/testdata/layer3/conf/layer.conf b/lib/layerindexlib/tests/testdata/layer3/conf/layer.conf
new file mode 100644
index 0000000..7089071
--- /dev/null
+++ b/lib/layerindexlib/tests/testdata/layer3/conf/layer.conf
@@ -0,0 +1,19 @@
+# We might have a conf and classes directory, append to BBPATH
+BBPATH .= ":${LAYERDIR}"
+
+# We have recipes directories, add to BBFILES
+BBFILES += "${LAYERDIR}/recipes*/*/*.bb ${LAYERDIR}/recipes*/*/*.bbappend"
+
+BBFILE_COLLECTIONS += "meta-python"
+BBFILE_PATTERN_meta-python := "^${LAYERDIR}/"
+BBFILE_PRIORITY_meta-python = "7"
+
+# This should only be incremented on significant changes that will
+# cause compatibility issues with other layers
+LAYERVERSION_meta-python = "1"
+
+LAYERDEPENDS_meta-python = "core openembedded-layer"
+
+LAYERSERIES_COMPAT_meta-python = "sumo"
+
+LICENSE_PATH += "${LAYERDIR}/licenses"
diff --git a/lib/layerindexlib/tests/testdata/layer4/conf/layer.conf b/lib/layerindexlib/tests/testdata/layer4/conf/layer.conf
new file mode 100644
index 0000000..6649ee0
--- /dev/null
+++ b/lib/layerindexlib/tests/testdata/layer4/conf/layer.conf
@@ -0,0 +1,22 @@
+# We have a conf and classes directory, append to BBPATH
+BBPATH .= ":${LAYERDIR}"
+
+# We have a recipes directory, add to BBFILES
+BBFILES += "${LAYERDIR}/recipes-*/*/*.bb ${LAYERDIR}/recipes-*/*/*.bbappend"
+
+BBFILE_COLLECTIONS += "openembedded-layer"
+BBFILE_PATTERN_openembedded-layer := "^${LAYERDIR}/"
+
+# Define the priority for recipes (.bb files) from this layer,
+# choosing carefully how this layer interacts with all of the
+# other layers.
+
+BBFILE_PRIORITY_openembedded-layer = "6"
+
+# This should only be incremented on significant changes that will
+# cause compatibility issues with other layers
+LAYERVERSION_openembedded-layer = "1"
+
+LAYERDEPENDS_openembedded-layer = "core"
+
+LAYERSERIES_COMPAT_openembedded-layer = "sumo"
--
1.8.3.1
More information about the bitbake-devel
mailing list