3 The Signature package for the scons software construction utility.
8 # Copyright (c) 2001, 2002 Steven Knight
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:
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
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.
30 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
38 #XXX Get rid of the global array so this becomes re-entrant.
43 for sig_file in sig_files:
47 def __init__(self, module, entry=None):
49 self.timestamp = self.csig = self.bsig = self.implicit = None
52 arr = map(string.strip, string.split(entry, " ", 3))
55 if arr[0] == '-': self.timestamp = None
56 else: self.timestamp = int(arr[0])
58 if arr[1] == '-': self.bsig = None
59 else: self.bsig = module.from_string(arr[1])
61 if arr[2] == '-': self.csig = None
62 else: self.csig = module.from_string(arr[2])
64 if arr[3] == '-': self.implicit = None
65 else: self.implicit = arr[3]
69 def render(self, module):
70 if self.timestamp is None: timestamp = '-'
71 else: timestamp = "%d"%self.timestamp
73 if self.bsig is None: bsig = '-'
74 else: bsig = module.to_string(self.bsig)
76 if self.csig is None: csig = '-'
77 else: csig = module.to_string(self.csig)
79 if self.implicit is None: implicit = '-'
80 else: implicit = self.implicit
82 return '%s %s %s %s' % (timestamp, bsig, csig, implicit)
84 def get_implicit(self):
85 if self.implicit is None:
88 return string.split(self.implicit, '\0')
90 def set_implicit(self, implicit):
94 self.implicit = string.join(map(str, implicit), '\0')
99 Encapsulates reading and writing a .sconsign file.
102 def __init__(self, dir, module=None):
104 dir - the directory for the file
105 module - the signature module being used
111 self.module = default_calc.module
114 self.sconsign = os.path.join(dir.path, '.sconsign')
119 file = open(self.sconsign, 'rt')
123 for line in file.readlines():
124 filename, rest = map(string.strip, string.split(line, ":", 1))
125 self.entries[filename] = SConsignEntry(self.module, rest)
128 sig_files.append(self)
130 def get(self, filename):
132 Get the .sconsign entry for a file
134 filename - the filename whose signature will be returned
135 returns - (timestamp, bsig, csig, implicit)
138 entry = self.entries[filename]
139 return (entry.timestamp, entry.bsig, entry.csig)
141 return (None, None, None)
143 def set(self, filename, timestamp, bsig = None, csig = None):
145 Set the .sconsign entry for a file
147 filename - the filename whose signature will be set
148 timestamp - the timestamp
149 module - the signature module being used
150 bsig - the file's build signature
151 csig - the file's content signature
155 entry = self.entries[filename]
157 entry = SConsignEntry(self.module)
158 self.entries[filename] = entry
160 entry.timestamp = timestamp
166 def get_implicit(self, filename):
167 """Fetch the cached implicit dependencies for 'filename'"""
169 entry = self.entries[filename]
170 return entry.get_implicit()
174 def set_implicit(self, filename, implicit):
175 """Cache the implicit dependencies for 'filename'."""
177 entry = self.entries[filename]
179 entry = SConsignEntry(self.module)
180 self.entries[filename] = entry
182 entry.set_implicit(implicit)
186 Write the .sconsign file to disk.
188 Try to write to a temporary file first, and rename it if we
189 succeed. If we can't write to the temporary file, it's
190 probably because the directory isn't writable (and if so,
191 how did we build anything in this directory, anyway?), so
192 try to write directly to the .sconsign file as a backup.
193 If we can't rename, try to copy the temporary contents back
194 to the .sconsign file. Either way, always try to remove
195 the temporary file at the end.
198 temp = os.path.join(self.dir.path, '.scons%d' % os.getpid())
200 file = open(temp, 'wt')
204 file = open(self.sconsign, 'wt')
205 fname = self.sconsign
208 keys = self.entries.keys()
211 file.write("%s: %s\n" % (name, self.entries[name].render(self.module)))
213 if fname != self.sconsign:
215 mode = os.stat(self.sconsign)[0]
216 os.chmod(self.sconsign, 0666)
217 os.unlink(self.sconsign)
221 os.rename(fname, self.sconsign)
223 open(self.sconsign, 'wb').write(open(fname, 'rb').read())
224 os.chmod(self.sconsign, mode)
233 Encapsulates signature calculations and .sconsign file generating
234 for the build engine.
237 def __init__(self, module=None, max_drift=2*24*60*60):
239 Initialize the calculator.
241 module - the signature module to use for signature calculations
242 max_drift - the maximum system clock drift used to determine when to
243 cache content signatures. A negative value means to never cache
244 content signatures. (defaults to 2 days)
251 self.max_drift = max_drift
253 def bsig(self, node):
255 Generate a node's build signature, the digested signatures
256 of its dependency files and build information.
258 node - the node whose sources will be collected
259 returns - the build signature
261 This no longer handles the recursive descent of the
262 node's children's signatures. We expect that they're
263 already built and updated by someone else, if that's
266 if not node.use_signature:
269 bsig = node.get_bsig()
273 sigs = map(self.get_signature, node.children())
275 sigs.append(self.module.signature(node.builder_sig_adapter()))
277 bsig = self.module.collect(filter(lambda x: not x is None, sigs))
281 # don't store the bsig here, because it isn't accurate until
282 # the node is actually built.
286 def csig(self, node):
288 Generate a node's content signature, the digested signature
292 returns - the content signature
294 if not node.use_signature:
297 csig = node.get_csig()
301 if self.max_drift >= 0:
302 info = node.get_prevsiginfo()
306 mtime = node.get_timestamp()
308 if (info and info[0] and info[2] and info[0] == mtime):
309 # use the signature stored in the .sconsign file
311 # Set the csig here so it doesn't get recalculated unnecessarily
312 # and so it's set when the .sconsign file gets written
315 csig = self.module.signature(node)
316 # Set the csig here so it doesn't get recalculated unnecessarily
317 # and so it's set when the .sconsign file gets written
320 if self.max_drift >= 0 and (time.time() - mtime) > self.max_drift:
325 def get_signature(self, node):
327 Get the appropriate signature for a node.
330 returns - the signature or None if the signature could not
333 This method does not store the signature in the node and
334 in the .sconsign file.
337 if not node.use_signature:
338 # This node type doesn't use a signature (e.g. a
339 # directory) so bail right away.
342 return self.bsig(node)
343 elif not node.exists():
346 return self.csig(node)
348 def current(self, node, newsig):
350 Check if a signature is up to date with respect to a node.
352 node - the node whose signature will be checked
353 newsig - the (presumably current) signature of the file
355 returns - 1 if the file is current with the specified signature,
361 # The node itself has told us whether or not it's
362 # current without checking the signature. The
363 # canonical uses here are a "0" return for a file
364 # that doesn't exist, or a directory.
367 oldtime, oldbsig, oldcsig = node.get_prevsiginfo()
369 if not node.builder and node.get_timestamp() == oldtime:
372 return self.module.current(newsig, oldbsig)
375 default_calc = Calculator()