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