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         self.cachedir = bb.data.getVar("CACHE", cooker.configuration.data, True)
52         self.cachefile = os.path.join(self.cachedir,"bb_cache.dat")
53         self.clean = {}
54         self.depends_cache = {}
55         self.data = None
56         self.data_fn = None
57
58         if self.cachedir in [None, '']:
59             if cooker.cb is not None:
60                 print "NOTE: Not using a cache. Set CACHE = <directory> to enable."
61         else:
62             if cooker.cb is not None:
63                 print "NOTE: Using cache in '%s'" % self.cachedir
64             try:
65                 os.stat( self.cachedir )
66             except OSError:
67                 bb.mkdirhier( self.cachedir )
68
69         if (self.mtime(self.cachefile)):
70             try:
71                 p = pickle.Unpickler( file(self.cachefile,"rb"))
72                 self.depends_cache, version_data = p.load()
73                 if version_data['CACHE_VER'] != __cache_version__:
74                     raise ValueError, 'Cache Version Mismatch'
75                 if version_data['BITBAKE_VER'] != bb.__version__:
76                     raise ValueError, 'Bitbake Version Mismatch'
77             except (ValueError, KeyError):
78                 bb.note("Invalid cache found, rebuilding...")
79                 self.depends_cache = {}
80
81         if self.depends_cache:
82             for fn in self.depends_cache.keys():
83                 self.clean[fn] = ""
84                 self.cacheValidUpdate(fn)
85
86     def getVar(self, var, fn, exp = 0):
87         """
88         Gets the value of a variable
89         (similar to getVar in the data class)
90         
91         There are two scenarios:
92           1. We have cached data - serve from depends_cache[fn]
93           2. We're learning what data to cache - serve from data 
94              backend but add a copy of the data to the cache.
95         """
96
97         if fn in self.clean:
98             return self.depends_cache[fn][var]
99
100         if not fn in self.depends_cache:
101             self.depends_cache[fn] = {}
102
103         if fn != self.data_fn:
104             # We're trying to access data in the cache which doesn't exist
105             # yet setData hasn't been called to setup the right access. Very bad.
106             bb.error("Parsing error data_fn %s and fn %s don't match" % (self.data_fn, fn))
107
108         result = bb.data.getVar(var, self.data, exp)
109         self.depends_cache[fn][var] = result
110         return result
111
112     def setData(self, fn, data):
113         """
114         Called to prime bb_cache ready to learn which variables to cache.
115         Will be followed by calls to self.getVar which aren't cached
116         but can be fulfilled from self.data.
117         """
118         self.data_fn = fn
119         self.data = data
120
121         # Make sure __depends makes the depends_cache
122         self.getVar("__depends", fn, True)
123         self.depends_cache[fn]["CACHETIMESTAMP"] = bb.parse.cached_mtime(fn)
124
125     def loadDataFull(self, fn, cooker):
126         """
127         Return a complete set of data for fn.
128         To do this, we need to parse the file.
129         """
130         bb_data, skipped = self.load_bbfile(fn, cooker)
131         return bb_data
132
133     def loadData(self, fn, cooker):
134         """
135         Load a subset of data for fn.
136         If the cached data is valid we do nothing,
137         To do this, we need to parse the file and set the system
138         to record the variables accessed.
139         Return the cache status and whether the file was skipped when parsed
140         """
141         if self.cacheValid(fn):
142             if "SKIPPED" in self.depends_cache[fn]:
143                 return True, True
144             return True, False
145
146         bb_data, skipped = self.load_bbfile(fn, cooker)
147         self.setData(fn, bb_data)
148         return False, skipped
149
150     def cacheValid(self, fn):
151         """
152         Is the cache valid for fn?
153         Fast version, no timestamps checked.
154         """
155         # Is cache enabled?
156         if self.cachedir in [None, '']:
157             return False
158         if fn in self.clean:
159             return True
160         return False
161
162     def cacheValidUpdate(self, fn):
163         """
164         Is the cache valid for fn?
165         Make thorough (slower) checks including timestamps.
166         """
167         # Is cache enabled?
168         if self.cachedir in [None, '']:
169             return False
170
171         # Check file still exists
172         if self.mtime(fn) == 0:
173             bb.debug(2, "Cache: %s not longer exists" % fn)
174             if fn in self.clean:
175                 del self.clean[fn]
176             if fn in self.depends_cache:
177                 del self.depends_cache[fn]
178             return False
179
180         # File isn't in depends_cache
181         if not fn in self.depends_cache:
182             bb.debug(2, "Cache: %s is not cached" % fn)
183             if fn in self.clean:
184                 del self.clean[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             if fn in self.clean:
191                 del self.clean[fn]
192             return False
193
194         # Check dependencies are still valid
195         depends = self.getVar("__depends", fn, True)
196         if depends:
197             deps = depends.split(" ")
198             for dep in deps:
199                 (f,old_mtime_s) = dep.split("@")
200                 old_mtime = int(old_mtime_s)
201                 new_mtime = bb.parse.cached_mtime(f)
202                 if (new_mtime > old_mtime):
203                     bb.debug(2, "Cache: %s's dependency %s changed" % (fn, f))
204                     if fn in self.clean:
205                         del self.clean[fn]
206                     return False
207
208         bb.debug(2, "Depends Cache: %s is clean" % fn)
209         if not fn in self.clean:
210             self.clean[fn] = ""
211
212         return True
213
214     def skip(self, fn):
215         """
216         Mark a fn as skipped
217         Called from the parser
218         """
219         if not fn in self.depends_cache:
220             self.depends_cache[fn] = {}
221         self.depends_cache[fn]["SKIPPED"] = "1"
222
223     def remove(self, fn):
224         """
225         Remove a fn from the cache
226         Called from the parser in error cases
227         """
228         bb.note("Removing %s from cache" % fn)
229         if fn in self.depends_cache:
230             del self.depends_cache[fn]
231
232     def sync(self):
233         """
234         Save the cache
235         Called from the parser when complete (or exitting)
236         """
237
238         version_data = {}
239         version_data['CACHE_VER'] = __cache_version__
240         version_data['BITBAKE_VER'] = bb.__version__
241
242         p = pickle.Pickler(file(self.cachefile, "wb" ), -1 )
243         p.dump([self.depends_cache, version_data])
244
245     def mtime(self, cachefile):
246         try:
247             return os.stat(cachefile)[8]
248         except OSError:
249             return 0
250
251     def load_bbfile( self, bbfile , cooker):
252         """
253         Load and parse one .bb build file
254         Return the data and whether parsing resulted in the file being skipped
255         """
256
257         import bb
258         from bb import utils, data, parse, debug, event, fatal
259
260         topdir = data.getVar('TOPDIR', cooker.configuration.data)
261         if not topdir:
262             topdir = os.path.abspath(os.getcwd())
263             # set topdir to here
264             data.setVar('TOPDIR', topdir, cooker.configuration)
265         bbfile = os.path.abspath(bbfile)
266         bbfile_loc = os.path.abspath(os.path.dirname(bbfile))
267         # expand tmpdir to include this topdir
268         data.setVar('TMPDIR', data.getVar('TMPDIR', cooker.configuration.data, 1) or "", cooker.configuration.data)
269         # set topdir to location of .bb file
270         topdir = bbfile_loc
271         #data.setVar('TOPDIR', topdir, cfg)
272         # go there
273         oldpath = os.path.abspath(os.getcwd())
274         os.chdir(topdir)
275         bb_data = data.init_db(cooker.configuration.data)
276         try:
277             parse.handle(bbfile, bb_data) # read .bb data
278             os.chdir(oldpath)
279             return bb_data, False
280         except bb.parse.SkipPackage:
281             os.chdir(oldpath)
282             return bb_data, True
283         except:
284             os.chdir(oldpath)
285             raise
286
287 def init(cooker):
288     """
289     The Objective: Cache the minimum amount of data possible yet get to the 
290     stage of building packages (i.e. tryBuild) without reparsing any .bb files.
291
292     To do this, we intercept getVar calls and only cache the variables we see 
293     being accessed. We rely on the cache getVar calls being made for all 
294     variables bitbake might need to use to reach this stage. For each cached 
295     file we need to track:
296
297     * Its mtime
298     * The mtimes of all its dependencies
299     * Whether it caused a parse.SkipPackage exception
300
301     Files causing parsing errors are evicted from the cache.
302
303     """
304     return Cache(cooker)
305