diff options
Diffstat (limited to 'bitbake/lib/layerindexlib/__init__.py')
-rw-r--r-- | bitbake/lib/layerindexlib/__init__.py | 1364 |
1 files changed, 1364 insertions, 0 deletions
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 | ||