bitbake/lib/bb/cache.py:
[bitbake.git] / lib / bb / cache.py
1 #!/usr/bin/env python
2 # ex:ts=4:sw=4:sts=4:et
3 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4 """
5 BitBake 'Event' implementation
6
7 Caching of bitbake variables before task execution
8
9 # Copyright (C) 2006        Richard Purdie
10
11 # but small sections based on code from bin/bitbake:
12 # Copyright (C) 2003, 2004  Chris Larson
13 # Copyright (C) 2003, 2004  Phil Blundell
14 # Copyright (C) 2003 - 2005 Michael 'Mickey' Lauer
15 # Copyright (C) 2005        Holger Hans Peter Freyther
16 # Copyright (C) 2005        ROAD GmbH
17
18 This program is free software; you can redistribute it and/or modify it under
19 the terms of the GNU General Public License as published by the Free Software
20 Foundation; either version 2 of the License, or (at your option) any later
21 version.
22
23 This program is distributed in the hope that it will be useful, but WITHOUT
24 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
25 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
26
27 You should have received a copy of the GNU General Public License along with
28 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
29 Place, Suite 330, Boston, MA 02111-1307 USA. 
30
31 """
32
33 import os, re
34 import bb.data
35 import bb.utils
36
37 try:
38     import cPickle as pickle
39 except ImportError:
40     import pickle
41     print "NOTE: Importing cPickle failed. Falling back to a very slow implementation."
42
43 __cache_version__ = "123"
44
45 class Cache:
46     """
47     BitBake Cache implementation
48     """
49     def __init__(self, cooker):
50
51
52         self.cachedir = bb.data.getVar("CACHE", cooker.configuration.data, True)
53         self.clean = {}
54         self.depends_cache = {}
55         self.data = None
56         self.data_fn = None
57
58         if self.cachedir in [None, '']:
59             self.has_cache = False
60             if cooker.cb is not None:
61                 print "NOTE: 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             if cooker.cb is not None:
67                 print "NOTE: Using cache in '%s'" % self.cachedir
68             try:
69                 os.stat( self.cachedir )
70             except OSError:
71                 bb.mkdirhier( self.cachedir )
72
73         if self.has_cache and (self.mtime(self.cachefile)):
74             try:
75                 p = pickle.Unpickler( file(self.cachefile,"rb"))
76                 self.depends_cache, version_data = p.load()
77                 if version_data['CACHE_VER'] != __cache_version__:
78                     raise ValueError, 'Cache Version Mismatch'
79                 if version_data['BITBAKE_VER'] != bb.__version__:
80                     raise ValueError, 'Bitbake Version Mismatch'
81             except (ValueError, KeyError):
82                 bb.note("Invalid cache found, rebuilding...")
83                 self.depends_cache = {}
84
85         if self.depends_cache:
86             for fn in self.depends_cache.keys():
87                 self.clean[fn] = ""
88                 self.cacheValidUpdate(fn)
89
90     def getVar(self, var, fn, exp = 0):
91         """
92         Gets the value of a variable
93         (similar to getVar in the data class)
94         
95         There are two scenarios:
96           1. We have cached data - serve from depends_cache[fn]
97           2. We're learning what data to cache - serve from data 
98              backend but add a copy of the data to the cache.
99         """
100
101         if fn in self.clean:
102             return self.depends_cache[fn][var]
103
104         if not fn in self.depends_cache:
105             self.depends_cache[fn] = {}
106
107         if fn != self.data_fn:
108             # We're trying to access data in the cache which doesn't exist
109             # yet setData hasn't been called to setup the right access. Very bad.
110             bb.error("Parsing error data_fn %s and fn %s don't match" % (self.data_fn, fn))
111
112         result = bb.data.getVar(var, self.data, exp)
113         self.depends_cache[fn][var] = result
114         return result
115
116     def setData(self, fn, data):
117         """
118         Called to prime bb_cache ready to learn which variables to cache.
119         Will be followed by calls to self.getVar which aren't cached
120         but can be fulfilled from self.data.
121         """
122         self.data_fn = fn
123         self.data = data
124
125         # Make sure __depends makes the depends_cache
126         self.getVar("__depends", fn, True)
127         self.depends_cache[fn]["CACHETIMESTAMP"] = bb.parse.cached_mtime(fn)
128
129     def loadDataFull(self, fn, cooker):
130         """
131         Return a complete set of data for fn.
132         To do this, we need to parse the file.
133         """
134         bb_data, skipped = self.load_bbfile(fn, cooker)
135         return bb_data
136
137     def loadData(self, fn, cooker):
138         """
139         Load a subset of data for fn.
140         If the cached data is valid we do nothing,
141         To do this, we need to parse the file and set the system
142         to record the variables accessed.
143         Return the cache status and whether the file was skipped when parsed
144         """
145         if self.cacheValid(fn):
146             if "SKIPPED" in self.depends_cache[fn]:
147                 return True, True
148             return True, False
149
150         bb_data, skipped = self.load_bbfile(fn, cooker)
151         self.setData(fn, bb_data)
152         return False, skipped
153
154     def cacheValid(self, fn):
155         """
156         Is the cache valid for fn?
157         Fast version, no timestamps checked.
158         """
159         # Is cache enabled?
160         if not self.has_cache:
161             return False
162         if fn in self.clean:
163             return True
164         return False
165
166     def cacheValidUpdate(self, fn):
167         """
168         Is the cache valid for fn?
169         Make thorough (slower) checks including timestamps.
170         """
171         # Is cache enabled?
172         if not self.has_cache:
173             return False
174
175         # Check file still exists
176         if self.mtime(fn) == 0:
177             bb.debug(2, "Cache: %s not longer exists" % fn)
178             self.remove(fn)
179             return False
180
181         # File isn't in depends_cache
182         if not fn in self.depends_cache:
183             bb.debug(2, "Cache: %s is not cached" % fn)
184             self.remove(fn)
185             return False
186
187         # Check the file's timestamp
188         if bb.parse.cached_mtime(fn) > self.getVar("CACHETIMESTAMP", fn, True):
189             bb.debug(2, "Cache: %s changed" % fn)
190             self.remove(fn)
191             return False
192
193         # Check dependencies are still valid
194         depends = self.getVar("__depends", fn, True)
195         if depends:
196             deps = depends.split(" ")
197             for dep in deps:
198                 (f,old_mtime_s) = dep.split("@")
199                 old_mtime = int(old_mtime_s)
200                 new_mtime = bb.parse.cached_mtime(f)
201                 if (new_mtime > old_mtime):
202                     bb.debug(2, "Cache: %s's dependency %s changed" % (fn, f))
203                     self.remove(fn)
204                     return False
205
206         bb.debug(2, "Depends Cache: %s is clean" % fn)
207         if not fn in self.clean:
208             self.clean[fn] = ""
209
210         return True
211
212     def skip(self, fn):
213         """
214         Mark a fn as skipped
215         Called from the parser
216         """
217         if not fn in self.depends_cache:
218             self.depends_cache[fn] = {}
219         self.depends_cache[fn]["SKIPPED"] = "1"
220
221     def remove(self, fn):
222         """
223         Remove a fn from the cache
224         Called from the parser in error cases
225         """
226         bb.debug(1, "Removing %s from cache" % fn)
227         if fn in self.depends_cache:
228             del self.depends_cache[fn]
229         if fn in self.clean:
230             del self.clean[fn]
231
232     def sync(self):
233         """
234         Save the cache
235         Called from the parser when complete (or exitting)
236         """
237
238         if not self.has_cache:
239             return
240
241         version_data = {}
242         version_data['CACHE_VER'] = __cache_version__
243         version_data['BITBAKE_VER'] = bb.__version__
244
245         p = pickle.Pickler(file(self.cachefile, "wb" ), -1 )
246         p.dump([self.depends_cache, version_data])
247
248     def mtime(self, cachefile):
249         try:
250             return os.stat(cachefile)[8]
251         except OSError:
252             return 0
253
254     def load_bbfile( self, bbfile , cooker):
255         """
256         Load and parse one .bb build file
257         Return the data and whether parsing resulted in the file being skipped
258         """
259
260         import bb
261         from bb import utils, data, parse, debug, event, fatal
262
263         topdir = data.getVar('TOPDIR', cooker.configuration.data)
264         if not topdir:
265             topdir = os.path.abspath(os.getcwd())
266             # set topdir to here
267             data.setVar('TOPDIR', topdir, cooker.configuration)
268         bbfile = os.path.abspath(bbfile)
269         bbfile_loc = os.path.abspath(os.path.dirname(bbfile))
270         # expand tmpdir to include this topdir
271         data.setVar('TMPDIR', data.getVar('TMPDIR', cooker.configuration.data, 1) or "", cooker.configuration.data)
272         # set topdir to location of .bb file
273         topdir = bbfile_loc
274         #data.setVar('TOPDIR', topdir, cfg)
275         # go there
276         oldpath = os.path.abspath(os.getcwd())
277         os.chdir(topdir)
278         bb_data = data.init_db(cooker.configuration.data)
279         try:
280             parse.handle(bbfile, bb_data) # read .bb data
281             os.chdir(oldpath)
282             return bb_data, False
283         except bb.parse.SkipPackage:
284             os.chdir(oldpath)
285             return bb_data, True
286         except:
287             os.chdir(oldpath)
288             raise
289
290 def init(cooker):
291     """
292     The Objective: Cache the minimum amount of data possible yet get to the 
293     stage of building packages (i.e. tryBuild) without reparsing any .bb files.
294
295     To do this, we intercept getVar calls and only cache the variables we see 
296     being accessed. We rely on the cache getVar calls being made for all 
297     variables bitbake might need to use to reach this stage. For each cached 
298     file we need to track:
299
300     * Its mtime
301     * The mtimes of all its dependencies
302     * Whether it caused a parse.SkipPackage exception
303
304     Files causing parsing errors are evicted from the cache.
305
306     """
307     return Cache(cooker)
308