summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/cache.py
diff options
context:
space:
mode:
authorChris Larson <chris_larson@mentor.com>2010-11-16 12:58:52 -0700
committerRichard Purdie <rpurdie@linux.intel.com>2011-01-04 14:46:42 +0000
commitacca3440579b5d5149bc951b6c6edafc018f45be (patch)
tree9d2951324b621297414c32bb1e882e3904db484f /bitbake/lib/bb/cache.py
parentc6328564de8e8cae113ee559d769105f9f4b6003 (diff)
downloadpoky-acca3440579b5d5149bc951b6c6edafc018f45be.tar.gz
cache: create and use a RecipeInfo class
This class holds the particular pieces of information about a recipe which are needed for runqueue to do its job. By using it, I think we improve code clarity, reduce method sizes, reduce overuse of primitive types, and prepare for parallel parsing. In addition, this ditches the leaky abstraction whereby bb.cache attempted to hide the difference between cached data and a full recipe parse. This was a remnant from the way things used to be done, and the code using it had to know the difference anyway. If we choose to reimplement caching of the full recipes, we can do it in bb.parse, in a completely transparent way. (Bitbake rev: 992cc252452221f5f23575e50eb67528b2838fdb) Signed-off-by: Chris Larson <chris_larson@mentor.com> Signed-off-by: Richard Purdie <rpurdie@linux.intel.com>
Diffstat (limited to 'bitbake/lib/bb/cache.py')
-rw-r--r--bitbake/lib/bb/cache.py369
1 files changed, 196 insertions, 173 deletions
diff --git a/bitbake/lib/bb/cache.py b/bitbake/lib/bb/cache.py
index 707d81c971..3e2f698cf7 100644
--- a/bitbake/lib/bb/cache.py
+++ b/bitbake/lib/bb/cache.py
@@ -30,7 +30,7 @@
30 30
31import os 31import os
32import logging 32import logging
33from collections import defaultdict 33from collections import defaultdict, namedtuple
34import bb.data 34import bb.data
35import bb.utils 35import bb.utils
36 36
@@ -43,7 +43,104 @@ except ImportError:
43 logger.info("Importing cPickle failed. " 43 logger.info("Importing cPickle failed. "
44 "Falling back to a very slow implementation.") 44 "Falling back to a very slow implementation.")
45 45
46__cache_version__ = "132" 46__cache_version__ = "133"
47
48recipe_fields = (
49 'pn',
50 'pv',
51 'pr',
52 'pe',
53 'defaultpref',
54 'depends',
55 'provides',
56 'task_deps',
57 'stamp',
58 'broken',
59 'not_world',
60 'skipped',
61 'timestamp',
62 'packages',
63 'packages_dynamic',
64 'rdepends',
65 'rdepends_pkg',
66 'rprovides',
67 'rprovides_pkg',
68 'rrecommends',
69 'rrecommends_pkg',
70 'nocache',
71 'variants',
72 'file_depends',
73 'tasks',
74 'basetaskhashes',
75 'hashfilename',
76)
77
78
79class RecipeInfo(namedtuple('RecipeInfo', recipe_fields)):
80 __slots__ = ()
81
82 @classmethod
83 def listvar(cls, var, metadata):
84 return cls.getvar(var, metadata).split()
85
86 @classmethod
87 def intvar(cls, var, metadata):
88 return int(cls.getvar(var, metadata) or 0)
89
90 @classmethod
91 def depvar(cls, var, metadata):
92 return bb.utils.explode_deps(cls.getvar(var, metadata))
93
94 @classmethod
95 def pkgvar(cls, var, packages, metadata):
96 return dict((pkg, cls.depvar("%s_%s" % (var, pkg), metadata))
97 for pkg in packages)
98
99 @classmethod
100 def taskvar(cls, var, tasks, metadata):
101 return dict((task, cls.getvar("%s_task-%s" % (var, task), metadata))
102 for task in tasks)
103 @classmethod
104 def getvar(cls, var, metadata):
105 return metadata.getVar(var, True) or ''
106
107 @classmethod
108 def from_metadata(cls, filename, metadata):
109 tasks = metadata.getVar('__BBTASKS', False)
110
111 packages = cls.listvar('PACKAGES', metadata)
112 return RecipeInfo(
113 tasks = tasks,
114 basetaskhashes = cls.taskvar('BB_BASEHASH', tasks, metadata),
115 hashfilename = cls.getvar('BB_HASHFILENAME', metadata),
116
117 file_depends = metadata.getVar('__depends', False),
118 task_deps = metadata.getVar('_task_deps', False) or
119 {'tasks': [], 'parents': {}},
120 variants = cls.listvar('__VARIANTS', metadata) + [''],
121
122 skipped = cls.getvar('__SKIPPED', metadata),
123 timestamp = bb.parse.cached_mtime(filename),
124 packages = packages,
125 pn = cls.getvar('PN', metadata),
126 pe = cls.getvar('PE', metadata),
127 pv = cls.getvar('PV', metadata),
128 pr = cls.getvar('PR', metadata),
129 nocache = cls.getvar('__BB_DONT_CACHE', metadata),
130 defaultpref = cls.intvar('DEFAULT_PREFERENCE', metadata),
131 broken = cls.getvar('BROKEN', metadata),
132 not_world = cls.getvar('EXCLUDE_FROM_WORLD', metadata),
133 stamp = cls.getvar('STAMP', metadata),
134 packages_dynamic = cls.listvar('PACKAGES_DYNAMIC', metadata),
135 depends = cls.depvar('DEPENDS', metadata),
136 provides = cls.depvar('PROVIDES', metadata),
137 rdepends = cls.depvar('RDEPENDS', metadata),
138 rprovides = cls.depvar('RPROVIDES', metadata),
139 rrecommends = cls.depvar('RRECOMMENDS', metadata),
140 rprovides_pkg = cls.pkgvar('RPROVIDES', packages, metadata),
141 rdepends_pkg = cls.pkgvar('RDEPENDS', packages, metadata),
142 rrecommends_pkg = cls.pkgvar('RRECOMMENDS', packages, metadata),
143 )
47 144
48 145
49class Cache(object): 146class Cache(object):
@@ -99,56 +196,6 @@ class Cache(object):
99 if os.path.isfile(self.cachefile): 196 if os.path.isfile(self.cachefile):
100 logger.info("Out of date cache found, rebuilding...") 197 logger.info("Out of date cache found, rebuilding...")
101 198
102 def getVar(self, var, fn, exp=0):
103 """
104 Gets the value of a variable
105 (similar to getVar in the data class)
106
107 There are two scenarios:
108 1. We have cached data - serve from depends_cache[fn]
109 2. We're learning what data to cache - serve from data
110 backend but add a copy of the data to the cache.
111 """
112 if fn in self.clean:
113 return self.depends_cache[fn].get(var)
114
115 self.depends_cache.setdefault(fn, {})
116
117 if fn != self.data_fn:
118 # We're trying to access data in the cache which doesn't exist
119 # yet setData hasn't been called to setup the right access
120 logger.error("data_fn %s and fn %s don't match", self.data_fn, fn)
121
122 self.cacheclean = False
123 result = bb.data.getVar(var, self.data, exp)
124 if result is not None:
125 self.depends_cache[fn][var] = result
126 return result
127
128 def setData(self, virtualfn, fn, data):
129 """
130 Called to prime bb_cache ready to learn which variables to cache.
131 Will be followed by calls to self.getVar which aren't cached
132 but can be fulfilled from self.data.
133 """
134 self.data_fn = virtualfn
135 self.data = data
136
137 # Make sure __depends makes the depends_cache
138 # If we're a virtual class we need to make sure all our depends are
139 # appended to the depends of fn.
140 depends = self.getVar("__depends", virtualfn) or set()
141 self.depends_cache.setdefault(fn, {})
142 if "__depends" not in self.depends_cache[fn] or not self.depends_cache[fn]["__depends"]:
143 self.depends_cache[fn]["__depends"] = depends
144 else:
145 self.depends_cache[fn]["__depends"].update(depends)
146
147 # Make sure the variants always make it into the cache too
148 self.getVar('__VARIANTS', virtualfn, True)
149
150 self.depends_cache[virtualfn]["CACHETIMESTAMP"] = bb.parse.cached_mtime(fn)
151
152 @staticmethod 199 @staticmethod
153 def virtualfn2realfn(virtualfn): 200 def virtualfn2realfn(virtualfn):
154 """ 201 """
@@ -193,38 +240,39 @@ class Cache(object):
193 to record the variables accessed. 240 to record the variables accessed.
194 Return the cache status and whether the file was skipped when parsed 241 Return the cache status and whether the file was skipped when parsed
195 """ 242 """
196 skipped = 0 243 skipped, virtuals = 0, 0
197 virtuals = 0
198 244
199 if fn not in self.checked: 245 if fn not in self.checked:
200 self.cacheValidUpdate(fn) 246 self.cacheValidUpdate(fn)
201 247
202 if self.cacheValid(fn): 248 cached = self.cacheValid(fn)
203 multi = self.getVar('__VARIANTS', fn, True) 249 if not cached:
204 for cls in (multi or "").split() + [""]: 250 self.cacheclean = False
205 virtualfn = self.realfn2virtual(fn, cls) 251 logger.debug(1, "Parsing %s", fn)
206 if self.depends_cache[virtualfn].get("__SKIPPED"): 252 datastores = self.load_bbfile(fn, appends, cfgData)
207 skipped += 1 253 depends = set()
208 logger.debug(1, "Skipping %s", virtualfn) 254 for variant, data in sorted(datastores.iteritems(),
209 continue 255 key=lambda i: i[0],
210 self.handle_data(virtualfn, cacheData) 256 reverse=True):
211 virtuals += 1 257 virtualfn = self.realfn2virtual(fn, variant)
212 return True, skipped, virtuals 258 depends |= (data.getVar("__depends", False) or set())
213 259 if depends and not variant:
214 logger.debug(1, "Parsing %s", fn) 260 data.setVar("__depends", depends)
215 261 info = RecipeInfo.from_metadata(fn, data)
216 bb_data = self.load_bbfile(fn, appends, cfgData) 262 self.depends_cache[virtualfn] = info
217 263
218 for data in bb_data: 264 info = self.depends_cache[fn]
219 virtualfn = self.realfn2virtual(fn, data) 265 for variant in info.variants:
220 self.setData(virtualfn, fn, bb_data[data]) 266 virtualfn = self.realfn2virtual(fn, variant)
221 if self.getVar("__SKIPPED", virtualfn): 267 vinfo = self.depends_cache[virtualfn]
222 skipped += 1 268 if vinfo.skipped:
223 logger.debug(1, "Skipping %s", virtualfn) 269 logger.debug(1, "Skipping %s", virtualfn)
270 skipped += 1
224 else: 271 else:
225 self.handle_data(virtualfn, cacheData) 272 cacheData.add_from_recipeinfo(virtualfn, vinfo)
226 virtuals += 1 273 virtuals += 1
227 return False, skipped, virtuals 274
275 return cached, skipped, virtuals
228 276
229 def cacheValid(self, fn): 277 def cacheValid(self, fn):
230 """ 278 """
@@ -266,14 +314,15 @@ class Cache(object):
266 self.remove(fn) 314 self.remove(fn)
267 return False 315 return False
268 316
317 info = self.depends_cache[fn]
269 # Check the file's timestamp 318 # Check the file's timestamp
270 if mtime != self.getVar("CACHETIMESTAMP", fn, True): 319 if mtime != info.timestamp:
271 logger.debug(2, "Cache: %s changed", fn) 320 logger.debug(2, "Cache: %s changed", fn)
272 self.remove(fn) 321 self.remove(fn)
273 return False 322 return False
274 323
275 # Check dependencies are still valid 324 # Check dependencies are still valid
276 depends = self.getVar("__depends", fn, True) 325 depends = info.file_depends
277 if depends: 326 if depends:
278 for f, old_mtime in depends: 327 for f, old_mtime in depends:
279 fmtime = bb.parse.cached_mtime_noerror(f) 328 fmtime = bb.parse.cached_mtime_noerror(f)
@@ -290,20 +339,17 @@ class Cache(object):
290 self.remove(fn) 339 self.remove(fn)
291 return False 340 return False
292 341
293 self.clean.add(fn)
294 invalid = False 342 invalid = False
295 # Mark extended class data as clean too 343 for cls in info.variants:
296 multi = self.getVar('__VARIANTS', fn, True)
297 for cls in (multi or "").split():
298 virtualfn = self.realfn2virtual(fn, cls) 344 virtualfn = self.realfn2virtual(fn, cls)
299 self.clean.add(virtualfn) 345 self.clean.add(virtualfn)
300 if not virtualfn in self.depends_cache: 346 if virtualfn not in self.depends_cache:
301 logger.debug(2, "Cache: %s is not cached", virtualfn) 347 logger.debug(2, "Cache: %s is not cached", virtualfn)
302 invalid = True 348 invalid = True
303 349
304 # If any one of the variants is not present, mark as invalid for all 350 # If any one of the variants is not present, mark as invalid for all
305 if invalid: 351 if invalid:
306 for cls in (multi or "").split(): 352 for cls in info.variants:
307 virtualfn = self.realfn2virtual(fn, cls) 353 virtualfn = self.realfn2virtual(fn, cls)
308 if virtualfn in self.clean: 354 if virtualfn in self.clean:
309 logger.debug(2, "Cache: Removing %s from cache", virtualfn) 355 logger.debug(2, "Cache: Removing %s from cache", virtualfn)
@@ -332,7 +378,6 @@ class Cache(object):
332 Save the cache 378 Save the cache
333 Called from the parser when complete (or exiting) 379 Called from the parser when complete (or exiting)
334 """ 380 """
335 import copy
336 381
337 if not self.has_cache: 382 if not self.has_cache:
338 return 383 return
@@ -345,13 +390,12 @@ class Cache(object):
345 version_data['CACHE_VER'] = __cache_version__ 390 version_data['CACHE_VER'] = __cache_version__
346 version_data['BITBAKE_VER'] = bb.__version__ 391 version_data['BITBAKE_VER'] = bb.__version__
347 392
348 cache_data = copy.copy(self.depends_cache) 393 cache_data = dict(self.depends_cache)
349 for fn in self.depends_cache: 394 for fn, info in self.depends_cache.iteritems():
350 if '__BB_DONT_CACHE' in self.depends_cache[fn] and self.depends_cache[fn]['__BB_DONT_CACHE']: 395 if info.nocache:
351 logger.debug(2, "Not caching %s, marked as not cacheable", fn) 396 logger.debug(2, "Not caching %s, marked as not cacheable", fn)
352 del cache_data[fn] 397 del cache_data[fn]
353 elif ('PV' in self.depends_cache[fn] and 398 elif info.pv and 'SRCREVINACTION' in info.pv:
354 'SRCREVINACTION' in self.depends_cache[fn]['PV']):
355 logger.error("Not caching %s as it had SRCREVINACTION in PV. " 399 logger.error("Not caching %s as it had SRCREVINACTION in PV. "
356 "Please report this bug", fn) 400 "Please report this bug", fn)
357 del cache_data[fn] 401 del cache_data[fn]
@@ -364,90 +408,14 @@ class Cache(object):
364 def mtime(cachefile): 408 def mtime(cachefile):
365 return bb.parse.cached_mtime_noerror(cachefile) 409 return bb.parse.cached_mtime_noerror(cachefile)
366 410
367 def handle_data(self, file_name, cacheData): 411 def add(self, file_name, data, cacheData):
368 """ 412 """
369 Save data we need into the cache 413 Save data we need into the cache
370 """ 414 """
371 415 realfn = self.virtualfn2realfn(file_name)[0]
372 pn = self.getVar('PN', file_name, True) 416 info = RecipeInfo.from_metadata(realfn, data)
373 pe = self.getVar('PE', file_name, True) or "0" 417 self.depends_cache[file_name] = info
374 pv = self.getVar('PV', file_name, True) 418 cacheData.add_from_recipeinfo(file_name, info)
375 if 'SRCREVINACTION' in pv:
376 logger.info("Found SRCREVINACTION in PV (%s) or %s. Please report this bug.", pv, file_name)
377 pr = self.getVar('PR', file_name, True)
378 dp = int(self.getVar('DEFAULT_PREFERENCE', file_name, True) or "0")
379 depends = bb.utils.explode_deps(self.getVar("DEPENDS", file_name, True) or "")
380 packages = (self.getVar('PACKAGES', file_name, True) or "").split()
381 packages_dynamic = (self.getVar('PACKAGES_DYNAMIC', file_name, True) or "").split()
382 rprovides = (self.getVar("RPROVIDES", file_name, True) or "").split()
383
384 cacheData.task_deps[file_name] = self.getVar("_task_deps", file_name)
385
386 # build PackageName to FileName lookup table
387 cacheData.pkg_pn[pn].append(file_name)
388
389 cacheData.stamp[file_name] = self.getVar('STAMP', file_name, True)
390
391 cacheData.tasks[file_name] = self.getVar('__BBTASKS', file_name, True)
392 for t in cacheData.tasks[file_name]:
393 cacheData.basetaskhash[file_name + "." + t] = self.getVar("BB_BASEHASH_task-%s" % t, file_name, True)
394
395 # build FileName to PackageName lookup table
396 cacheData.pkg_fn[file_name] = pn
397 cacheData.pkg_pepvpr[file_name] = (pe, pv, pr)
398 cacheData.pkg_dp[file_name] = dp
399
400 provides = [pn]
401 for provide in (self.getVar("PROVIDES", file_name, True) or "").split():
402 if provide not in provides:
403 provides.append(provide)
404
405 # Build forward and reverse provider hashes
406 # Forward: virtual -> [filenames]
407 # Reverse: PN -> [virtuals]
408 cacheData.fn_provides[file_name] = provides
409 for provide in provides:
410 cacheData.providers[provide].append(file_name)
411 if not provide in cacheData.pn_provides[pn]:
412 cacheData.pn_provides[pn].append(provide)
413
414 for dep in depends:
415 if not dep in cacheData.deps[file_name]:
416 cacheData.deps[file_name].append(dep)
417 if not dep in cacheData.all_depends:
418 cacheData.all_depends.append(dep)
419
420 # Build reverse hash for PACKAGES, so runtime dependencies
421 # can be be resolved (RDEPENDS, RRECOMMENDS etc.)
422 for package in packages:
423 cacheData.packages[package].append(file_name)
424 rprovides += (self.getVar("RPROVIDES_%s" % package, file_name, 1) or "").split()
425
426 for package in packages_dynamic:
427 cacheData.packages_dynamic[package].append(file_name)
428
429 for rprovide in rprovides:
430 cacheData.rproviders[rprovide].append(file_name)
431
432 # Build hash of runtime depends and rececommends
433 rdepends = bb.utils.explode_deps(self.getVar('RDEPENDS', file_name, True) or "")
434 rrecommends = bb.utils.explode_deps(self.getVar('RRECOMMENDS', file_name, True) or "")
435 for package in packages + [pn]:
436 rdeps_pkg = bb.utils.explode_deps(self.getVar('RDEPENDS_%s' % package, file_name, True) or "")
437 cacheData.rundeps[file_name][package] = rdepends + rdeps_pkg
438 rrecs_pkg = bb.utils.explode_deps(self.getVar('RDEPENDS_%s' % package, file_name, True) or "")
439 cacheData.runrecs[file_name][package] = rrecommends + rrecs_pkg
440
441 # Collect files we may need for possible world-dep
442 # calculations
443 if not self.getVar('BROKEN', file_name, True) and not self.getVar('EXCLUDE_FROM_WORLD', file_name, True):
444 cacheData.possible_world.append(file_name)
445
446 cacheData.hashfn[file_name] = self.getVar('BB_HASHFILENAME', file_name, True)
447
448 # Touch this to make sure its in the cache
449 self.getVar('__BB_DONT_CACHE', file_name, True)
450 self.getVar('__VARIANTS', file_name, True)
451 419
452 @staticmethod 420 @staticmethod
453 def load_bbfile(bbfile, appends, config): 421 def load_bbfile(bbfile, appends, config):
@@ -514,7 +482,6 @@ class CacheData(object):
514 def __init__(self): 482 def __init__(self):
515 """ 483 """
516 Direct cache variables 484 Direct cache variables
517 (from Cache.handle_data)
518 """ 485 """
519 self.providers = defaultdict(list) 486 self.providers = defaultdict(list)
520 self.rproviders = defaultdict(list) 487 self.rproviders = defaultdict(list)
@@ -547,3 +514,59 @@ class CacheData(object):
547 self.world_target = set() 514 self.world_target = set()
548 self.bbfile_priority = {} 515 self.bbfile_priority = {}
549 self.bbfile_config_priorities = [] 516 self.bbfile_config_priorities = []
517
518 def add_from_recipeinfo(self, fn, info):
519 self.task_deps[fn] = info.task_deps
520 self.pkg_fn[fn] = info.pn
521 self.pkg_pn[info.pn].append(fn)
522 self.pkg_pepvpr[fn] = (info.pe, info.pv, info.pr)
523 self.pkg_dp[fn] = info.defaultpref
524 self.stamp[fn] = info.stamp
525
526 provides = [info.pn]
527 for provide in info.provides:
528 if provide not in provides:
529 provides.append(provide)
530 self.fn_provides[fn] = provides
531
532 for provide in provides:
533 self.providers[provide].append(fn)
534 if provide not in self.pn_provides[info.pn]:
535 self.pn_provides[info.pn].append(provide)
536
537 for dep in info.depends:
538 if dep not in self.deps[fn]:
539 self.deps[fn].append(dep)
540 if dep not in self.all_depends:
541 self.all_depends.append(dep)
542
543 rprovides = info.rprovides
544 for package in info.packages:
545 self.packages[package].append(fn)
546 rprovides += info.rprovides_pkg[package]
547
548 for package in info.packages_dynamic:
549 self.packages_dynamic[package].append(fn)
550
551 for rprovide in rprovides:
552 self.rproviders[rprovide].append(fn)
553
554 # Build hash of runtime depends and rececommends
555 for package in info.packages + [info.pn]:
556 rundeps, runrecs = list(info.rdepends), list(info.rrecommends)
557 if package in info.packages:
558 rundeps += info.rdepends_pkg[package]
559 runrecs += info.rrecommends_pkg[package]
560 self.rundeps[fn][package] = rundeps
561 self.runrecs[fn][package] = runrecs
562
563 # Collect files we may need for possible world-dep
564 # calculations
565 if not info.broken and not info.not_world:
566 self.possible_world.append(fn)
567
568 self.hashfn[fn] = info.hashfilename
569
570 for task, taskhash in info.basetaskhashes.iteritems():
571 identifier = '%s.%s' % (fn, task)
572 self.basetaskhash[identifier] = taskhash