a91817b3d33161546f6e821644723f6dba4166be
[scons.git] / src / engine / SCons / SConsign.py
1 """SCons.SConsign
2
3 Writing and reading information to the .sconsign file or files.
4
5 """
6
7 #
8 # __COPYRIGHT__
9 #
10 # Permission is hereby granted, free of charge, to any person obtaining
11 # a copy of this software and associated documentation files (the
12 # "Software"), to deal in the Software without restriction, including
13 # without limitation the rights to use, copy, modify, merge, publish,
14 # distribute, sublicense, and/or sell copies of the Software, and to
15 # permit persons to whom the Software is furnished to do so, subject to
16 # the following conditions:
17 #
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
20 #
21 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
22 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
23 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 #
29
30 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
31
32 import cPickle
33 import os
34 import os.path
35 import time
36
37 import SCons.Sig
38 import SCons.Node
39 import SCons.Warnings
40
41 #XXX Get rid of the global array so this becomes re-entrant.
42 sig_files = []
43
44 database = None
45
46 def write():
47     global sig_files
48     for sig_file in sig_files:
49         sig_file.write()
50
51
52 class Entry:
53
54     """Objects of this type are pickled to the .sconsign file, so it
55     should only contain simple builtin Python datatypes and no methods.
56
57     This class is used to store cache information about nodes between
58     scons runs for efficiency, and to store the build signature for
59     nodes so that scons can determine if they are out of date. """
60
61     # setup the default value for various attributes:
62     # (We make the class variables so the default values won't get pickled
63     # with the instances, which would waste a lot of space)
64     timestamp = None
65     bsig = None
66     csig = None
67     implicit = None
68     bkids = []
69     bkidsigs = []
70     bact = None
71     bactsig = None
72
73 class Base:
74     """
75     This is the controlling class for the signatures for the collection of
76     entries associated with a specific directory.  The actual directory
77     association will be maintained by a subclass that is specific to
78     the underlying storage method.  This class provides a common set of
79     methods for fetching and storing the individual bits of information
80     that make up signature entry.
81     """
82     def __init__(self, module=None):
83         """
84         module - the signature module being used
85         """
86
87         self.module = module or SCons.Sig.default_calc.module
88         self.entries = {}
89         self.dirty = 0
90
91     # A null .sconsign entry.  We define this here so that it will
92     # be easy to keep this in sync if/whenever we change the type of
93     # information returned by the get() method, below.
94     null_siginfo = (None, None, None)
95
96     def get(self, filename):
97         """
98         Get the .sconsign entry for a file
99
100         filename - the filename whose signature will be returned
101         returns - (timestamp, bsig, csig)
102         """
103         entry = self.get_entry(filename)
104         return (entry.timestamp, entry.bsig, entry.csig)
105
106     def get_entry(self, filename):
107         """
108         Create an entry for the filename and return it, or if one already exists,
109         then return it.
110         """
111         try:
112             return self.entries[filename]
113         except (KeyError, AttributeError):
114             return Entry()
115
116     def set_entry(self, filename, entry):
117         """
118         Set the entry.
119         """
120         self.entries[filename] = entry
121         self.dirty = 1
122
123     def set_csig(self, filename, csig):
124         """
125         Set the csig .sconsign entry for a file
126
127         filename - the filename whose signature will be set
128         csig - the file's content signature
129         """
130
131         entry = self.get_entry(filename)
132         entry.csig = csig
133         self.set_entry(filename, entry)
134
135     def set_binfo(self, filename, bsig, bkids, bkidsigs, bact, bactsig):
136         """
137         Set the build info .sconsign entry for a file
138
139         filename - the filename whose signature will be set
140         bsig - the file's built signature
141         """
142
143         entry = self.get_entry(filename)
144         entry.bsig = bsig
145         entry.bkids = bkids
146         entry.bkidsigs = bkidsigs
147         entry.bact = bact
148         entry.bactsig = bactsig
149         self.set_entry(filename, entry)
150
151     def set_timestamp(self, filename, timestamp):
152         """
153         Set the timestamp .sconsign entry for a file
154
155         filename - the filename whose signature will be set
156         timestamp - the file's timestamp
157         """
158
159         entry = self.get_entry(filename)
160         entry.timestamp = timestamp
161         self.set_entry(filename, entry)
162
163     def get_implicit(self, filename):
164         """Fetch the cached implicit dependencies for 'filename'"""
165         entry = self.get_entry(filename)
166         return entry.implicit
167
168     def set_implicit(self, filename, implicit):
169         """Cache the implicit dependencies for 'filename'."""
170         entry = self.get_entry(filename)
171         if not SCons.Util.is_List(implicit):
172             implicit = [implicit]
173         implicit = map(str, implicit)
174         entry.implicit = implicit
175         self.set_entry(filename, entry)
176
177     def get_binfo(self, filename):
178         """Fetch the cached implicit dependencies for 'filename'"""
179         entry = self.get_entry(filename)
180         return entry.bsig, entry.bkids, entry.bkidsigs, entry.bact, entry.bactsig
181
182 class DB(Base):
183     """
184     A Base subclass that reads and writes signature information
185     from a global .sconsign.dbm file.
186     """
187     def __init__(self, dir, module=None):
188         Base.__init__(self, module)
189
190         self.dir = dir
191
192         try:
193             global database
194             rawentries = database[self.dir.path]
195         except KeyError:
196             pass
197         else:
198             try:
199                 self.entries = cPickle.loads(rawentries)
200                 if type(self.entries) is not type({}):
201                     self.entries = {}
202                     raise TypeError
203             except KeyboardInterrupt:
204                 raise
205             except:
206                 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
207                                     "Ignoring corrupt sconsign entry : %s"%self.dir.path)
208
209         global sig_files
210         sig_files.append(self)
211
212     def write(self):
213         if self.dirty:
214             global database
215             database[self.dir.path] = cPickle.dumps(self.entries, 1)
216             try:
217                 database.sync()
218             except AttributeError:
219                 # Not all anydbm modules have sync() methods.
220                 pass
221
222 class Dir(Base):
223     def __init__(self, fp=None, module=None):
224         """
225         fp - file pointer to read entries from
226         module - the signature module being used
227         """
228         Base.__init__(self, module)
229
230         if fp:
231             self.entries = cPickle.load(fp)
232             if type(self.entries) is not type({}):
233                 self.entries = {}
234                 raise TypeError
235
236 class DirFile(Dir):
237     """
238     Encapsulates reading and writing a per-directory .sconsign file.
239     """
240     def __init__(self, dir, module=None):
241         """
242         dir - the directory for the file
243         module - the signature module being used
244         """
245
246         self.dir = dir
247         self.sconsign = os.path.join(dir.path, '.sconsign')
248
249         try:
250             fp = open(self.sconsign, 'rb')
251         except IOError:
252             fp = None
253
254         try:
255             Dir.__init__(self, fp, module)
256         except KeyboardInterrupt:
257             raise
258         except:
259             SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
260                                 "Ignoring corrupt .sconsign file: %s"%self.sconsign)
261
262         global sig_files
263         sig_files.append(self)
264
265     def write(self):
266         """
267         Write the .sconsign file to disk.
268
269         Try to write to a temporary file first, and rename it if we
270         succeed.  If we can't write to the temporary file, it's
271         probably because the directory isn't writable (and if so,
272         how did we build anything in this directory, anyway?), so
273         try to write directly to the .sconsign file as a backup.
274         If we can't rename, try to copy the temporary contents back
275         to the .sconsign file.  Either way, always try to remove
276         the temporary file at the end.
277         """
278         if self.dirty:
279             temp = os.path.join(self.dir.path, '.scons%d' % os.getpid())
280             try:
281                 file = open(temp, 'wb')
282                 fname = temp
283             except IOError:
284                 try:
285                     file = open(self.sconsign, 'wb')
286                     fname = self.sconsign
287                 except IOError:
288                     return
289             cPickle.dump(self.entries, file, 1)
290             file.close()
291             if fname != self.sconsign:
292                 try:
293                     mode = os.stat(self.sconsign)[0]
294                     os.chmod(self.sconsign, 0666)
295                     os.unlink(self.sconsign)
296                 except OSError:
297                     pass
298                 try:
299                     os.rename(fname, self.sconsign)
300                 except OSError:
301                     open(self.sconsign, 'wb').write(open(fname, 'rb').read())
302                     os.chmod(self.sconsign, mode)
303             try:
304                 os.unlink(temp)
305             except OSError:
306                 pass
307
308 ForDirectory = DirFile
309
310 def File(name, dbm_module=None):
311     """
312     Arrange for all signatures to be stored in a global .sconsign.dbm
313     file.
314     """
315     global database
316     if database is None:
317         if dbm_module is None:
318             import SCons.dblite
319             dbm_module = SCons.dblite
320         database = dbm_module.open(name, "c")
321
322     global ForDirectory
323     ForDirectory = DB