Remove digraph and switch to new stamp checking code.
[bitbake.git] / lib / bb / cache.py
1 # ex:ts=4:sw=4:sts=4:et
2 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3 #
4 # BitBake 'Event' implementation
5 #
6 # Caching of bitbake variables before task execution
7
8 # Copyright (C) 2006        Richard Purdie
9
10 # but small sections based on code from bin/bitbake:
11 # Copyright (C) 2003, 2004  Chris Larson
12 # Copyright (C) 2003, 2004  Phil Blundell
13 # Copyright (C) 2003 - 2005 Michael 'Mickey' Lauer
14 # Copyright (C) 2005        Holger Hans Peter Freyther
15 # Copyright (C) 2005        ROAD GmbH
16 #
17 # This program is free software; you can redistribute it and/or modify
18 # it under the terms of the GNU General Public License version 2 as
19 # published by the Free Software Foundation.
20 #
21 # This program is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24 # GNU General Public License for more details.
25 #
26 # You should have received a copy of the GNU General Public License along
27 # with this program; if not, write to the Free Software Foundation, Inc.,
28 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
29
30
31 import os, re
32 import bb.data
33 import bb.utils
34 from sets import Set
35
36 try:
37     import cPickle as pickle
38 except ImportError:
39     import pickle
40     bb.msg.note(1, bb.msg.domain.Cache, "Importing cPickle failed. Falling back to a very slow implementation.")
41
42 __cache_version__ = "127"
43
44 class Cache:
45     """
46     BitBake Cache implementation
47     """
48     def __init__(self, cooker):
49
50
51         self.cachedir = bb.data.getVar("CACHE", cooker.configuration.data, True)
52         self.clean = {}
53         self.checked = {}
54         self.depends_cache = {}
55         self.data = None
56         self.data_fn = None
57         self.cacheclean = True
58
59         if self.cachedir in [None, '']:
60             self.has_cache = False
61             bb.msg.note(1, bb.msg.domain.Cache, "Not using a cache. Set CACHE = <directory> to enable.")
62         else:
63             self.has_cache = True
64             self.cachefile = os.path.join(self.cachedir,"bb_cache.dat")
65             
66             bb.msg.debug(1, bb.msg.domain.Cache, "Using cache in '%s'" % self.cachedir)
67             try:
68                 os.stat( self.cachedir )
69             except OSError:
70                 bb.mkdirhier( self.cachedir )
71
72         if not self.has_cache:
73             return            
74
75         # If any of configuration.data's dependencies are newer than the
76         # cache there isn't even any point in loading it...
77         newest_mtime = 0
78         deps = bb.data.getVar("__depends", cooker.configuration.data, True)
79         for f,old_mtime in deps:
80             if old_mtime > newest_mtime:
81                 newest_mtime = old_mtime
82
83         if self.mtime(self.cachefile) >= newest_mtime:
84             try:
85                 p = pickle.Unpickler(file(self.cachefile, "rb"))
86                 self.depends_cache, version_data = p.load()
87                 if version_data['CACHE_VER'] != __cache_version__:
88                     raise ValueError, 'Cache Version Mismatch'
89                 if version_data['BITBAKE_VER'] != bb.__version__:
90                     raise ValueError, 'Bitbake Version Mismatch'
91             except EOFError:
92                 bb.msg.note(1, bb.msg.domain.Cache, "Truncated cache found, rebuilding...")
93                 self.depends_cache = {}
94             except (ValueError, KeyError):
95                 bb.msg.note(1, bb.msg.domain.Cache, "Invalid cache found, rebuilding...")
96                 self.depends_cache = {}
97         else:
98             bb.msg.note(1, bb.msg.domain.Cache, "Out of date cache found, rebuilding...")
99
100     def getVar(self, var, fn, exp = 0):
101         """
102         Gets the value of a variable
103         (similar to getVar in the data class)
104         
105         There are two scenarios:
106           1. We have cached data - serve from depends_cache[fn]
107           2. We're learning what data to cache - serve from data 
108              backend but add a copy of the data to the cache.
109         """
110         if fn in self.clean:
111             return self.depends_cache[fn][var]
112
113         if not fn in self.depends_cache:
114             self.depends_cache[fn] = {}
115
116         if fn != self.data_fn:
117             # We're trying to access data in the cache which doesn't exist
118             # yet setData hasn't been called to setup the right access. Very bad.
119             bb.msg.error(bb.msg.domain.Cache, "Parsing error data_fn %s and fn %s don't match" % (self.data_fn, fn))
120
121         self.cacheclean = False
122         result = bb.data.getVar(var, self.data, exp)
123         self.depends_cache[fn][var] = result
124         return result
125
126     def setData(self, fn, data):
127         """
128         Called to prime bb_cache ready to learn which variables to cache.
129         Will be followed by calls to self.getVar which aren't cached
130         but can be fulfilled from self.data.
131         """
132         self.data_fn = fn
133         self.data = data
134
135         # Make sure __depends makes the depends_cache
136         self.getVar("__depends", fn, True)
137         self.depends_cache[fn]["CACHETIMESTAMP"] = bb.parse.cached_mtime(fn)
138
139     def loadDataFull(self, fn, cfgData):
140         """
141         Return a complete set of data for fn.
142         To do this, we need to parse the file.
143         """
144         bb.msg.debug(1, bb.msg.domain.Cache, "Parsing %s (full)" % fn)
145
146         bb_data, skipped = self.load_bbfile(fn, cfgData)
147         return bb_data
148
149     def loadData(self, fn, cfgData):
150         """
151         Load a subset of data for fn.
152         If the cached data is valid we do nothing,
153         To do this, we need to parse the file and set the system
154         to record the variables accessed.
155         Return the cache status and whether the file was skipped when parsed
156         """
157         if fn not in self.checked:
158             self.cacheValidUpdate(fn)
159         if self.cacheValid(fn):
160             if "SKIPPED" in self.depends_cache[fn]:
161                 return True, True
162             return True, False
163
164         bb.msg.debug(1, bb.msg.domain.Cache, "Parsing %s" % fn)
165
166         bb_data, skipped = self.load_bbfile(fn, cfgData)
167         self.setData(fn, bb_data)
168         return False, skipped
169
170     def cacheValid(self, fn):
171         """
172         Is the cache valid for fn?
173         Fast version, no timestamps checked.
174         """
175         # Is cache enabled?
176         if not self.has_cache:
177             return False
178         if fn in self.clean:
179             return True
180         return False
181
182     def cacheValidUpdate(self, fn):
183         """
184         Is the cache valid for fn?
185         Make thorough (slower) checks including timestamps.
186         """
187         # Is cache enabled?
188         if not self.has_cache:
189             return False
190
191         self.checked[fn] = ""
192
193         # Pretend we're clean so getVar works
194         self.clean[fn] = ""
195
196         # File isn't in depends_cache
197         if not fn in self.depends_cache:
198             bb.msg.debug(2, bb.msg.domain.Cache, "Cache: %s is not cached" % fn)
199             self.remove(fn)
200             return False
201
202         # Check file still exists
203         if self.mtime(fn) == 0:
204             bb.msg.debug(2, bb.msg.domain.Cache, "Cache: %s not longer exists" % fn)
205             self.remove(fn)
206             return False
207
208         # Check the file's timestamp
209         if bb.parse.cached_mtime(fn) > self.getVar("CACHETIMESTAMP", fn, True):
210             bb.msg.debug(2, bb.msg.domain.Cache, "Cache: %s changed" % fn)
211             self.remove(fn)
212             return False
213
214         # Check dependencies are still valid
215         depends = self.getVar("__depends", fn, True)
216         for f,old_mtime in depends:
217             # Check if file still exists
218             if self.mtime(f) == 0:
219                 self.remove(fn)
220                 return False
221
222             new_mtime = bb.parse.cached_mtime(f)
223             if (new_mtime > old_mtime):
224                 bb.msg.debug(2, bb.msg.domain.Cache, "Cache: %s's dependency %s changed" % (fn, f))
225                 self.remove(fn)
226                 return False
227
228         #bb.msg.debug(2, bb.msg.domain.Cache, "Depends Cache: %s is clean" % fn)
229         if not fn in self.clean:
230             self.clean[fn] = ""
231
232         return True
233
234     def skip(self, fn):
235         """
236         Mark a fn as skipped
237         Called from the parser
238         """
239         if not fn in self.depends_cache:
240             self.depends_cache[fn] = {}
241         self.depends_cache[fn]["SKIPPED"] = "1"
242
243     def remove(self, fn):
244         """
245         Remove a fn from the cache
246         Called from the parser in error cases
247         """
248         bb.msg.debug(1, bb.msg.domain.Cache, "Removing %s from cache" % fn)
249         if fn in self.depends_cache:
250             del self.depends_cache[fn]
251         if fn in self.clean:
252             del self.clean[fn]
253
254     def sync(self):
255         """
256         Save the cache
257         Called from the parser when complete (or exiting)
258         """
259
260         if not self.has_cache:
261             return
262
263         if self.cacheclean:
264             bb.msg.note(1, bb.msg.domain.Cache, "Cache is clean, not saving.")
265             return
266
267         version_data = {}
268         version_data['CACHE_VER'] = __cache_version__
269         version_data['BITBAKE_VER'] = bb.__version__
270
271         p = pickle.Pickler(file(self.cachefile, "wb" ), -1 )
272         p.dump([self.depends_cache, version_data])
273
274     def mtime(self, cachefile):
275         return bb.parse.cached_mtime_noerror(cachefile)
276
277     def handle_data(self, file_name, cacheData):
278         """
279         Save data we need into the cache 
280         """
281
282         pn       = self.getVar('PN', file_name, True)
283         pe       = self.getVar('PE', file_name, True) or "0"
284         pv       = self.getVar('PV', file_name, True)
285         pr       = self.getVar('PR', file_name, True)
286         dp       = int(self.getVar('DEFAULT_PREFERENCE', file_name, True) or "0")
287         provides  = Set([pn] + (self.getVar("PROVIDES", file_name, True) or "").split())
288         depends   = bb.utils.explode_deps(self.getVar("DEPENDS", file_name, True) or "")
289         packages  = (self.getVar('PACKAGES', file_name, True) or "").split()
290         packages_dynamic = (self.getVar('PACKAGES_DYNAMIC', file_name, True) or "").split()
291         rprovides = (self.getVar("RPROVIDES", file_name, True) or "").split()
292
293         cacheData.task_deps[file_name] = self.getVar("_task_deps", file_name, True)
294
295         # build PackageName to FileName lookup table
296         if pn not in cacheData.pkg_pn:
297             cacheData.pkg_pn[pn] = []
298         cacheData.pkg_pn[pn].append(file_name)
299
300         cacheData.stamp[file_name] = self.getVar('STAMP', file_name, True)
301
302         # build FileName to PackageName lookup table
303         cacheData.pkg_fn[file_name] = pn
304         cacheData.pkg_pepvpr[file_name] = (pe,pv,pr)
305         cacheData.pkg_dp[file_name] = dp
306
307         # Build forward and reverse provider hashes
308         # Forward: virtual -> [filenames]
309         # Reverse: PN -> [virtuals]
310         if pn not in cacheData.pn_provides:
311             cacheData.pn_provides[pn] = Set()
312         cacheData.pn_provides[pn] |= provides
313
314         cacheData.fn_provides[file_name] = Set()
315         for provide in provides:
316             if provide not in cacheData.providers:
317                 cacheData.providers[provide] = []
318             cacheData.providers[provide].append(file_name)
319             cacheData.fn_provides[file_name].add(provide)
320
321         cacheData.deps[file_name] = Set()
322         for dep in depends:
323             cacheData.all_depends.add(dep)
324             cacheData.deps[file_name].add(dep)
325
326         # Build reverse hash for PACKAGES, so runtime dependencies 
327         # can be be resolved (RDEPENDS, RRECOMMENDS etc.)
328         for package in packages:
329             if not package in cacheData.packages:
330                 cacheData.packages[package] = []
331             cacheData.packages[package].append(file_name)
332             rprovides += (self.getVar("RPROVIDES_%s" % package, file_name, 1) or "").split() 
333
334         for package in packages_dynamic:
335             if not package in cacheData.packages_dynamic:
336                 cacheData.packages_dynamic[package] = []
337             cacheData.packages_dynamic[package].append(file_name)
338
339         for rprovide in rprovides:
340             if not rprovide in cacheData.rproviders:
341                 cacheData.rproviders[rprovide] = []
342             cacheData.rproviders[rprovide].append(file_name)
343
344         # Build hash of runtime depends and rececommends
345
346         def add_dep(deplist, deps):
347             for dep in deps:
348                 if not dep in deplist:
349                     deplist[dep] = ""
350
351         if not file_name in cacheData.rundeps:
352             cacheData.rundeps[file_name] = {}
353         if not file_name in cacheData.runrecs:
354             cacheData.runrecs[file_name] = {}
355
356         rdepends = bb.utils.explode_deps(self.getVar('RDEPENDS', file_name, True) or "")
357         rrecommends = bb.utils.explode_deps(self.getVar('RRECOMMENDS', file_name, True) or "")
358         for package in packages + [pn]:
359             if not package in cacheData.rundeps[file_name]:
360                 cacheData.rundeps[file_name][package] = {}
361             if not package in cacheData.runrecs[file_name]:
362                 cacheData.runrecs[file_name][package] = {}
363
364             add_dep(cacheData.rundeps[file_name][package], rdepends)
365             add_dep(cacheData.runrecs[file_name][package], rrecommends)
366             add_dep(cacheData.rundeps[file_name][package], bb.utils.explode_deps(self.getVar("RDEPENDS_%s" % package, file_name, True) or ""))
367             add_dep(cacheData.runrecs[file_name][package], bb.utils.explode_deps(self.getVar("RRECOMMENDS_%s" % package, file_name, True) or ""))
368
369         # Collect files we may need for possible world-dep
370         # calculations
371         if not self.getVar('BROKEN', file_name, True) and not self.getVar('EXCLUDE_FROM_WORLD', file_name, True):
372             cacheData.possible_world.append(file_name)
373
374
375     def load_bbfile( self, bbfile , config):
376         """
377         Load and parse one .bb build file
378         Return the data and whether parsing resulted in the file being skipped
379         """
380
381         import bb
382         from bb import utils, data, parse, debug, event, fatal
383
384         # expand tmpdir to include this topdir
385         data.setVar('TMPDIR', data.getVar('TMPDIR', config, 1) or "", config)
386         bbfile_loc = os.path.abspath(os.path.dirname(bbfile))
387         oldpath = os.path.abspath(os.getcwd())
388         if self.mtime(bbfile_loc):
389             os.chdir(bbfile_loc)
390         bb_data = data.init_db(config)
391         try:
392             bb_data = parse.handle(bbfile, bb_data) # read .bb data
393             os.chdir(oldpath)
394             return bb_data, False
395         except bb.parse.SkipPackage:
396             os.chdir(oldpath)
397             return bb_data, True
398         except:
399             os.chdir(oldpath)
400             raise
401
402 def init(cooker):
403     """
404     The Objective: Cache the minimum amount of data possible yet get to the 
405     stage of building packages (i.e. tryBuild) without reparsing any .bb files.
406
407     To do this, we intercept getVar calls and only cache the variables we see 
408     being accessed. We rely on the cache getVar calls being made for all 
409     variables bitbake might need to use to reach this stage. For each cached 
410     file we need to track:
411
412     * Its mtime
413     * The mtimes of all its dependencies
414     * Whether it caused a parse.SkipPackage exception
415
416     Files causing parsing errors are evicted from the cache.
417
418     """
419     return Cache(cooker)
420
421
422
423 #============================================================================#
424 # CacheData
425 #============================================================================#
426 class CacheData:
427     """
428     The data structures we compile from the cached data
429     """
430
431     def __init__(self):
432         """
433         Direct cache variables
434         (from Cache.handle_data)
435         """
436         self.providers   = {}
437         self.rproviders = {}
438         self.packages = {}
439         self.packages_dynamic = {}
440         self.possible_world = []
441         self.pkg_pn = {}
442         self.pkg_fn = {}
443         self.pkg_pepvpr = {}
444         self.pkg_dp = {}
445         self.pn_provides = {}
446         self.fn_provides = {}
447         self.all_depends = Set()
448         self.deps = {}
449         self.rundeps = {}
450         self.runrecs = {}
451         self.task_queues = {}
452         self.task_deps = {}
453         self.stamp = {}
454         self.preferred = {}
455
456         """
457         Indirect Cache variables
458         (set elsewhere)
459         """
460         self.ignored_dependencies = []
461         self.world_target = Set()
462         self.bbfile_priority = {}
463         self.bbfile_config_priorities = []