Refactor how actions get executed to eliminate a lot of redundant signature calcualat...
[scons.git] / src / engine / SCons / Sig / __init__.py
1 """SCons.Sig
2
3 The Signature package for the scons software construction utility.
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.Node
38 import SCons.Warnings
39
40 try:
41     import MD5
42     default_module = MD5
43 except ImportError:
44     import TimeStamp
45     default_module = TimeStamp
46
47 default_max_drift = 2*24*60*60
48
49 #XXX Get rid of the global array so this becomes re-entrant.
50 sig_files = []
51
52 # 1 means use build signature for derived source files
53 # 0 means use content signature for derived source files
54 build_signature = 1
55
56 def write():
57     global sig_files
58     for sig_file in sig_files:
59         sig_file.write()
60
61 class SConsignEntry:
62
63     """Objects of this type are pickled to the .sconsign file, so it
64     should only contain simple builtin Python datatypes and no methods.
65
66     This class is used to store cache information about nodes between
67     scons runs for efficiency, and to store the build signature for
68     nodes so that scons can determine if they are out of date. """
69
70     # setup the default value for various attributes:
71     # (We make the class variables so the default values won't get pickled
72     # with the instances, which would waste a lot of space)
73     timestamp = None
74     bsig = None
75     csig = None
76     implicit = None
77
78 class SConsignFile:
79     """
80     Encapsulates reading and writing a .sconsign file.
81     """
82
83     def __init__(self, dir, module=None):
84         """
85         dir - the directory for the file
86         module - the signature module being used
87         """
88
89         self.dir = dir
90
91         if module is None:
92             self.module = default_calc.module
93         else:
94             self.module = module
95         self.sconsign = os.path.join(dir.path, '.sconsign')
96         self.entries = {}
97         self.dirty = 0
98
99         try:
100             file = open(self.sconsign, 'rb')
101         except:
102             pass
103         else:
104             try:
105                 self.entries = cPickle.load(file)
106                 if type(self.entries) is not type({}):
107                     self.entries = {}
108                     raise TypeError
109             except:
110                 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
111                                     "Ignoring corrupt .sconsign file: %s"%self.sconsign)
112
113         global sig_files
114         sig_files.append(self)
115
116     def get(self, filename):
117         """
118         Get the .sconsign entry for a file
119
120         filename - the filename whose signature will be returned
121         returns - (timestamp, bsig, csig)
122         """
123         entry = self.get_entry(filename)
124         return (entry.timestamp, entry.bsig, entry.csig)
125
126     def get_entry(self, filename):
127         """
128         Create an entry for the filename and return it, or if one already exists,
129         then return it.
130         """
131         try:
132             return self.entries[filename]
133         except:
134             return SConsignEntry()
135
136     def set_entry(self, filename, entry):
137         """
138         Set the entry.
139         """
140         self.entries[filename] = entry
141         self.dirty = 1
142
143     def set_csig(self, filename, csig):
144         """
145         Set the csig .sconsign entry for a file
146
147         filename - the filename whose signature will be set
148         csig - the file's content signature
149         """
150
151         entry = self.get_entry(filename)
152         entry.csig = csig
153         self.set_entry(filename, entry)
154
155     def set_bsig(self, filename, bsig):
156         """
157         Set the csig .sconsign entry for a file
158
159         filename - the filename whose signature will be set
160         bsig - the file's built signature
161         """
162
163         entry = self.get_entry(filename)
164         entry.bsig = bsig
165         self.set_entry(filename, entry)
166
167     def set_timestamp(self, filename, timestamp):
168         """
169         Set the csig .sconsign entry for a file
170
171         filename - the filename whose signature will be set
172         timestamp - the file's timestamp
173         """
174
175         entry = self.get_entry(filename)
176         entry.timestamp = timestamp
177         self.set_entry(filename, entry)
178
179     def get_implicit(self, filename):
180         """Fetch the cached implicit dependencies for 'filename'"""
181         entry = self.get_entry(filename)
182         return entry.implicit
183
184     def set_implicit(self, filename, implicit):
185         """Cache the implicit dependencies for 'filename'."""
186         entry = self.get_entry(filename)
187         if SCons.Util.is_String(implicit):
188             implicit = [implicit]
189         implicit = map(str, implicit)
190         entry.implicit = implicit
191         self.set_entry(filename, entry)
192
193     def write(self):
194         """
195         Write the .sconsign file to disk.
196
197         Try to write to a temporary file first, and rename it if we
198         succeed.  If we can't write to the temporary file, it's
199         probably because the directory isn't writable (and if so,
200         how did we build anything in this directory, anyway?), so
201         try to write directly to the .sconsign file as a backup.
202         If we can't rename, try to copy the temporary contents back
203         to the .sconsign file.  Either way, always try to remove
204         the temporary file at the end.
205         """
206         if self.dirty:
207             temp = os.path.join(self.dir.path, '.scons%d' % os.getpid())
208             try:
209                 file = open(temp, 'wb')
210                 fname = temp
211             except:
212                 try:
213                     file = open(self.sconsign, 'wb')
214                     fname = self.sconsign
215                 except:
216                     return
217             cPickle.dump(self.entries, file, 1)
218             file.close()
219             if fname != self.sconsign:
220                 try:
221                     mode = os.stat(self.sconsign)[0]
222                     os.chmod(self.sconsign, 0666)
223                     os.unlink(self.sconsign)
224                 except:
225                     pass
226                 try:
227                     os.rename(fname, self.sconsign)
228                 except:
229                     open(self.sconsign, 'wb').write(open(fname, 'rb').read())
230                     os.chmod(self.sconsign, mode)
231             try:
232                 os.unlink(temp)
233             except:
234                 pass
235
236 class Calculator:
237     """
238     Encapsulates signature calculations and .sconsign file generating
239     for the build engine.
240     """
241
242     def __init__(self, module=default_module, max_drift=default_max_drift):
243         """
244         Initialize the calculator.
245
246         module - the signature module to use for signature calculations
247         max_drift - the maximum system clock drift used to determine when to
248           cache content signatures. A negative value means to never cache
249           content signatures. (defaults to 2 days)
250         """
251         self.module = module
252         self.max_drift = max_drift
253
254     def bsig(self, node, cache=None):
255         """
256         Generate a node's build signature, the digested signatures
257         of its dependency files and build information.
258
259         node - the node whose sources will be collected
260         cache - alternate node to use for the signature cache
261         returns - the build signature
262
263         This no longer handles the recursive descent of the
264         node's children's signatures.  We expect that they're
265         already built and updated by someone else, if that's
266         what's wanted.
267         """
268
269         if cache is None: cache = node
270
271         bsig = cache.get_bsig()
272         if bsig is not None:
273             return bsig
274
275         children = node.children()
276
277         # double check bsig, because the call to childre() above may
278         # have set it:
279         bsig = cache.get_bsig()
280         if bsig is not None:
281             return bsig
282         
283         sigs = map(lambda n, c=self: n.calc_signature(c), children)
284         if node.has_builder():
285             sigs.append(self.module.signature(node.get_executor()))
286
287         bsig = self.module.collect(filter(lambda x: not x is None, sigs))
288
289         cache.set_bsig(bsig)
290
291         # don't store the bsig here, because it isn't accurate until
292         # the node is actually built.
293
294         return bsig
295
296     def csig(self, node, cache=None):
297         """
298         Generate a node's content signature, the digested signature
299         of its content.
300
301         node - the node
302         cache - alternate node to use for the signature cache
303         returns - the content signature
304         """
305
306         if cache is None: cache = node
307
308         csig = cache.get_csig()
309         if csig is not None:
310             return csig
311         
312         if self.max_drift >= 0:
313             info = node.get_prevsiginfo()
314         else:
315             info = None
316
317         mtime = node.get_timestamp()
318
319         if (info and info[0] and info[2] and info[0] == mtime):
320             # use the signature stored in the .sconsign file
321             csig = info[2]
322             # Set the csig here so it doesn't get recalculated unnecessarily
323             # and so it's set when the .sconsign file gets written
324             cache.set_csig(csig)
325         else:
326             csig = self.module.signature(node)
327             # Set the csig here so it doesn't get recalculated unnecessarily
328             # and so it's set when the .sconsign file gets written
329             cache.set_csig(csig)
330
331             if self.max_drift >= 0 and (time.time() - mtime) > self.max_drift:
332                 node.store_csig()
333                 node.store_timestamp()
334
335         return csig
336
337     def current(self, node, newsig):
338         """
339         Check if a signature is up to date with respect to a node.
340
341         node - the node whose signature will be checked
342         newsig - the (presumably current) signature of the file
343
344         returns - 1 if the file is current with the specified signature,
345         0 if it isn't
346         """
347         oldtime, oldbsig, oldcsig = node.get_prevsiginfo()
348
349         if not node.has_builder() and node.get_timestamp() == oldtime:
350             return 1
351
352         return self.module.current(newsig, oldbsig)
353
354
355 default_calc = Calculator()