bitbake/lib/bb/data.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 class Cache:
44
45     def __init__(self, cooker):
46
47         self.cachedir = bb.data.getVar("CACHE", cooker.configuration.data, True)
48         self.cachefile = os.path.join(self.cachedir,"bb_cache.dat")
49         self.clean = {}
50         self.depends_cache = {}
51         self.data = None
52         self.data_fn = None
53
54         if self.cachedir in [None, '']:
55             if cooker.cb is not None:
56                 print "NOTE: Not using a cache. Set CACHE = <directory> to enable."
57         else:
58             if cooker.cb is not None:
59                 print "NOTE: Using cache in '%s'" % self.cachedir
60             try:
61                 os.stat( self.cachedir )
62             except OSError:
63                 bb.mkdirhier( self.cachedir )
64
65         if (self.mtime(self.cachefile)):
66             p = pickle.Unpickler( file(self.cachefile,"rb"))
67             self.depends_cache = p.load()
68         if self.depends_cache:
69             for fn in self.depends_cache.keys():
70                 self.clean[fn] = ""
71                 self.cacheValidUpdate(fn)
72
73     def getVar(self, var, fn, exp = 0):
74         if fn in self.clean:
75             return self.depends_cache[fn][var]
76
77         if not fn in self.depends_cache:
78             self.depends_cache[fn] = {}
79
80         if fn != self.data_fn:
81             bb.fatal("Parsing error data_fn %s and fn %s don't match" % (self.data_fn, fn))
82
83         result = bb.data.getVar(var, self.data, exp)
84         self.depends_cache[fn][var] = result
85         return result
86
87     def setData(self, fn, data):
88         self.data_fn = fn
89         self.data = data
90
91         # Make sure __depends makes the depends_cache
92         self.getVar("__depends", fn, True)
93         self.depends_cache[fn]["CACHETIMESTAMP"] = bb.parse.cached_mtime(fn)
94
95     def loadDataFull(self, fn, cooker):
96
97         bb_data, skipped = self.load_bbfile(fn, cooker)
98         return bb_data, False
99
100     def loadData(self, fn, cooker):
101         if self.cacheValid(fn):
102             if "SKIPPED" in self.depends_cache[fn]:
103                 return True, True
104             return True, False
105
106         bb_data, skipped = self.load_bbfile(fn, cooker)
107         self.setData(fn, bb_data)
108         return False, skipped
109
110     def cacheValid(self, fn):
111         # Is cache enabled?
112         if self.cachedir in [None, '']:
113             return False
114         if fn in self.clean:
115             return True
116         return False
117
118     def cacheValidUpdate(self, fn):
119         # Is cache enabled?
120         if self.cachedir in [None, '']:
121             return False
122
123         # File isn't in depends_cache
124         if not fn in self.depends_cache:
125             bb.note("Cache: %s is not cached" % fn)
126             if fn in self.clean:
127                 del self.clean[fn]
128             return False
129
130         # Check the file's timestamp
131         if bb.parse.cached_mtime(fn) > self.getVar("CACHETIMESTAMP", fn, True):
132             bb.note("Cache: %s changed" % fn)
133             if fn in self.clean:
134                 del self.clean[fn]
135             return False
136
137         # Check dependencies are still valid
138         depends = self.getVar("__depends", fn, True)
139         if depends:
140             deps = depends.split(" ")
141             for dep in deps:
142                 (f,old_mtime_s) = dep.split("@")
143                 old_mtime = int(old_mtime_s)
144                 new_mtime = bb.parse.cached_mtime(f)
145                 if (new_mtime > old_mtime):
146                     bb.note("Cache: %s's dependency %s changed" % (fn, f))
147                     if fn in self.clean:
148                         del self.clean[fn]
149                     return False
150
151         #bb.note("Depends Cache: %s is clean" % fn)
152         if not fn in self.clean:
153             self.clean[fn] = ""
154
155         return True
156
157     def skip(self, fn):
158         if not fn in self.depends_cache:
159             self.depends_cache[fn] = {}
160         self.depends_cache[fn]["SKIPPED"] = "1"
161
162     def remove(self, fn):
163         bb.note("Removing %s from cache" % fn)
164         if fn in self.depends_cache:
165             del self.depends_cache[fn]
166
167     def sync(self):
168         p = pickle.Pickler(file(self.cachefile, "wb" ), -1 )
169         p.dump(self.depends_cache)
170
171     def mtime(self, cachefile):
172         try:
173             return os.stat(cachefile)[8]
174         except OSError:
175             return 0
176
177     def load_bbfile( self, bbfile , cooker):
178         """Load and parse one .bb build file"""
179
180         import bb
181         from bb import utils, data, parse, debug, event, fatal
182
183         topdir = data.getVar('TOPDIR', cooker.configuration.data)
184         if not topdir:
185             topdir = os.path.abspath(os.getcwd())
186             # set topdir to here
187             data.setVar('TOPDIR', topdir, cooker.configuration)
188         bbfile = os.path.abspath(bbfile)
189         bbfile_loc = os.path.abspath(os.path.dirname(bbfile))
190         # expand tmpdir to include this topdir
191         data.setVar('TMPDIR', data.getVar('TMPDIR', cooker.configuration.data, 1) or "", cooker.configuration.data)
192         # set topdir to location of .bb file
193         topdir = bbfile_loc
194         #data.setVar('TOPDIR', topdir, cfg)
195         # go there
196         oldpath = os.path.abspath(os.getcwd())
197         os.chdir(topdir)
198         bb_data = data.init_db(cooker.configuration.data)
199         try:
200             parse.handle(bbfile, bb_data) # read .bb data
201             os.chdir(oldpath)
202             return bb_data, False
203         except bb.parse.SkipPackage:
204             os.chdir(oldpath)
205             return bb_data, True
206         except:
207             os.chdir(oldpath)
208             raise
209
210 def init(cooker):
211     return Cache(cooker)
212