Signature performance improvements (Charles Crain).
[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 (c) 2001 Steven Knight
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 os.path
33 import string
34 import SCons.Node
35
36 #XXX Get rid of the global array so this becomes re-entrant.
37 sig_files = []
38
39 def write():
40     global sig_files
41     for sig_file in sig_files:
42         sig_file.write()
43
44 class SConsignFile:
45     """
46     Encapsulates reading and writing a .sconsign file.
47     """
48
49     def __init__(self, dir, module):
50         """
51         dir - the directory for the file
52         module - the signature module being used
53         """
54         
55         self.dir = dir
56         self.module = module
57         self.sconsign = os.path.join(dir.path, '.sconsign')
58         self.entries = {}
59         self.dirty = None
60                     
61         try:
62             file = open(self.sconsign, 'rt')
63         except:
64             pass
65         else:
66             for line in file.readlines():
67                 filename, rest = map(string.strip, string.split(line, ":"))
68                 self.entries[filename] = rest
69
70         global sig_files
71         sig_files.append(self)
72
73     def get(self, filename):
74         """
75         Get the .sconsign entry for a file
76
77         filename - the filename whose signature will be returned
78         returns - (timestamp, bsig, csig)
79         """
80         
81         try:
82             arr = map(string.strip, string.split(self.entries[filename], " "))
83         except KeyError:
84             return (None, None, None)
85         try:
86             if arr[1] == '-': bsig = None
87             else:             bsig = self.module.from_string(arr[1])
88         except IndexError:
89             bsig = None
90         try:
91             if arr[2] == '-': csig = None
92             else:             csig = self.module.from_string(arr[2])
93         except IndexError:
94             csig = None
95         return (int(arr[0]), bsig, csig)
96
97     def set(self, filename, timestamp, bsig = None, csig = None):
98         """
99         Set the .sconsign entry for a file
100
101         filename - the filename whose signature will be set
102         timestamp - the timestamp
103         module - the signature module being used
104         bsig - the file's build signature
105         csig - the file's content signature
106         """
107         if bsig is None: bsig = '-'
108         else:            bsig = self.module.to_string(bsig)
109         if csig is None: csig = ''
110         else:            csig = ' ' + self.module.to_string(csig)
111         self.entries[filename] = "%d %s%s" % (timestamp, bsig, csig)
112         self.dirty = 1
113
114     def write(self):
115         """
116         Write the .sconsign file to disk.
117         """
118         if self.dirty:
119             try:
120                 file = open(self.sconsign, 'wt')
121             except:
122                 pass
123             else:
124                 keys = self.entries.keys()
125                 keys.sort()
126                 for name in keys:
127                     file.write("%s: %s\n" % (name, self.entries[name]))
128
129
130 class Calculator:
131     """
132     Encapsulates signature calculations and .sconsign file generating
133     for the build engine.
134     """
135
136     def __init__(self, module):
137         """
138         Initialize the calculator.
139
140         module - the signature module to use for signature calculations
141         """
142         self.module = module
143
144     def bsig(self, node):
145         """
146         Generate a node's build signature, the digested signatures
147         of its dependency files and build information.
148
149         node - the node whose sources will be collected
150         returns - the build signature
151
152         This no longer handles the recursive descent of the
153         node's children's signatures.  We expect that they're
154         already built and updated by someone else, if that's
155         what's wanted.
156         """
157         if not node.use_signature:
158             return None
159         #XXX If configured, use the content signatures from the
160         #XXX .sconsign file if the timestamps match.
161
162         bsig = node.get_bsig()
163         if not bsig is None:
164             return bsig
165
166         # Collect the signatures for ALL the nodes that this
167         # node depends on. Just collecting the direct
168         # dependants is not good enough, because
169         # the signature of a non-derived file does
170         # not include the signatures of its psuedo-sources
171         # (e.g. the signature for a .c file does not include
172         # the signatures of the .h files that it includes).
173
174         # However, we do NOT want to walk dependencies of non-
175         # derived files, because calling get_signature() on the
176         # derived nodes will in turn call bsig() again and do that
177         # for us.  Hence:
178         def walk_non_derived(n, myself=node):
179             if not n.builder or n is myself:
180                 return n.children()
181             return []
182         walker = SCons.Node.Walker(node, walk_non_derived)
183         sigs = []
184         while 1:
185             child = walker.next()
186             if child is None: break
187             if child is node: continue # skip the node itself
188             sigs.append(self.get_signature(child))
189
190         if node.builder:
191             sigs.append(self.module.signature(node.builder_sig_adapter()))
192         return self.module.collect(filter(lambda x: not x is None, sigs))
193
194     def csig(self, node):
195         """
196         Generate a node's content signature, the digested signature
197         of its content.
198
199         node - the node
200         returns - the content signature
201         """
202         if not node.use_signature:
203             return None
204         #XXX If configured, use the content signatures from the
205         #XXX .sconsign file if the timestamps match.
206         csig = node.get_csig()
207         if not csig is None:
208             return csig
209         
210         return self.module.signature(node)
211
212     def get_signature(self, node):
213         """
214         Get the appropriate signature for a node.
215
216         node - the node
217         returns - the signature or None if the signature could not
218         be computed.
219
220         This method does not store the signature in the node and
221         in the .sconsign file.
222         """
223
224         if not node.use_signature:
225             # This node type doesn't use a signature (e.g. a
226             # directory) so bail right away.
227             return None
228         elif node.builder:
229             return self.bsig(node)
230         elif not node.exists():
231             return None
232         else:
233             return self.csig(node)
234
235     def current(self, node, newsig):
236         """
237         Check if a signature is up to date with respect to a node.
238
239         node - the node whose signature will be checked
240         newsig - the (presumably current) signature of the file
241
242         returns - 1 if the file is current with the specified signature,
243         0 if it isn't
244         """
245
246         c = node.current()
247         if not c is None:
248             # The node itself has told us whether or not it's
249             # current without checking the signature.  The
250             # canonical uses here are a "0" return for a file
251             # that doesn't exist, or a directory.
252             return c
253
254         oldtime, oldbsig, oldcsig = node.get_prevsiginfo()
255
256         if not node.builder and node.get_timestamp() == oldtime:
257             return 1
258         
259         return self.module.current(newsig, oldbsig)