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