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 len(arr) < 4: self.implicit = None # pre-0.07 format
65 elif arr[3] == '': self.implicit = '' # pre-0.08 format
66 elif arr[3] == '-': self.implicit = None
67 else: self.implicit = string.replace(arr[3], '\0\0', '')
71 def render(self, module):
72 if self.timestamp is None: timestamp = '-'
73 else: timestamp = "%d"%self.timestamp
75 if self.bsig is None: bsig = '-'
76 else: bsig = module.to_string(self.bsig)
78 if self.csig is None: csig = '-'
79 else: csig = module.to_string(self.csig)
81 if self.implicit is None: implicit = '-'
82 else: implicit = '\0\0%s\0\0'%self.implicit
84 return '%s %s %s %s' % (timestamp, bsig, csig, implicit)
86 def get_implicit(self):
87 if self.implicit is None:
89 elif self.implicit == '':
92 return string.split(self.implicit, '\0')
94 def set_implicit(self, implicit):
98 if SCons.Util.is_String(implicit):
100 self.implicit = string.join(map(str, implicit), '\0')
105 Encapsulates reading and writing a .sconsign file.
108 def __init__(self, dir, module=None):
110 dir - the directory for the file
111 module - the signature module being used
117 self.module = default_calc.module
120 self.sconsign = os.path.join(dir.path, '.sconsign')
125 file = open(self.sconsign, 'rt')
129 for line in file.readlines():
130 filename, rest = map(string.strip, string.split(line, ":", 1))
131 self.entries[filename] = SConsignEntry(self.module, rest)
134 sig_files.append(self)
136 def get(self, filename):
138 Get the .sconsign entry for a file
140 filename - the filename whose signature will be returned
141 returns - (timestamp, bsig, csig)
144 entry = self.entries[filename]
145 return (entry.timestamp, entry.bsig, entry.csig)
147 return (None, None, None)
149 def create_entry(self, filename):
151 Create an entry for the filename and return it, or if one already exists,
155 entry = self.entries[filename]
157 entry = SConsignEntry(self.module)
158 self.entries[filename] = entry
162 def set_csig(self, filename, csig):
164 Set the csig .sconsign entry for a file
166 filename - the filename whose signature will be set
167 csig - the file's content signature
170 entry = self.create_entry(filename)
174 def set_bsig(self, filename, bsig):
176 Set the csig .sconsign entry for a file
178 filename - the filename whose signature will be set
179 bsig - the file's built signature
182 entry = self.create_entry(filename)
186 def set_timestamp(self, filename, timestamp):
188 Set the csig .sconsign entry for a file
190 filename - the filename whose signature will be set
191 timestamp - the file's timestamp
194 entry = self.create_entry(filename)
195 entry.timestamp = timestamp
198 def get_implicit(self, filename):
199 """Fetch the cached implicit dependencies for 'filename'"""
201 entry = self.entries[filename]
202 return entry.get_implicit()
206 def set_implicit(self, filename, implicit):
207 """Cache the implicit dependencies for 'filename'."""
208 entry = self.create_entry(filename)
209 entry.set_implicit(implicit)
214 Write the .sconsign file to disk.
216 Try to write to a temporary file first, and rename it if we
217 succeed. If we can't write to the temporary file, it's
218 probably because the directory isn't writable (and if so,
219 how did we build anything in this directory, anyway?), so
220 try to write directly to the .sconsign file as a backup.
221 If we can't rename, try to copy the temporary contents back
222 to the .sconsign file. Either way, always try to remove
223 the temporary file at the end.
226 temp = os.path.join(self.dir.path, '.scons%d' % os.getpid())
228 file = open(temp, 'wt')
232 file = open(self.sconsign, 'wt')
233 fname = self.sconsign
236 keys = self.entries.keys()
239 file.write("%s: %s\n" % (name, self.entries[name].render(self.module)))
241 if fname != self.sconsign:
243 mode = os.stat(self.sconsign)[0]
244 os.chmod(self.sconsign, 0666)
245 os.unlink(self.sconsign)
249 os.rename(fname, self.sconsign)
251 open(self.sconsign, 'wb').write(open(fname, 'rb').read())
252 os.chmod(self.sconsign, mode)
261 Encapsulates signature calculations and .sconsign file generating
262 for the build engine.
265 def __init__(self, module=None, max_drift=2*24*60*60):
267 Initialize the calculator.
269 module - the signature module to use for signature calculations
270 max_drift - the maximum system clock drift used to determine when to
271 cache content signatures. A negative value means to never cache
272 content signatures. (defaults to 2 days)
279 # fallback on timestamp signatures if MD5 is not available
280 # XXX add a warning message here
282 self.module = TimeStamp
285 self.max_drift = max_drift
287 def bsig(self, node):
289 Generate a node's build signature, the digested signatures
290 of its dependency files and build information.
292 node - the node whose sources will be collected
293 returns - the build signature
295 This no longer handles the recursive descent of the
296 node's children's signatures. We expect that they're
297 already built and updated by someone else, if that's
300 if not node.use_signature:
303 bsig = node.get_bsig()
307 sigs = map(self.get_signature, node.children())
309 sigs.append(self.module.signature(node.builder_sig_adapter()))
311 bsig = self.module.collect(filter(lambda x: not x is None, sigs))
315 # don't store the bsig here, because it isn't accurate until
316 # the node is actually built.
320 def csig(self, node):
322 Generate a node's content signature, the digested signature
326 returns - the content signature
328 if not node.use_signature:
331 csig = node.get_csig()
335 if self.max_drift >= 0:
336 info = node.get_prevsiginfo()
340 mtime = node.get_timestamp()
342 if (info and info[0] and info[2] and info[0] == mtime):
343 # use the signature stored in the .sconsign file
345 # Set the csig here so it doesn't get recalculated unnecessarily
346 # and so it's set when the .sconsign file gets written
349 csig = self.module.signature(node)
350 # Set the csig here so it doesn't get recalculated unnecessarily
351 # and so it's set when the .sconsign file gets written
354 if self.max_drift >= 0 and (time.time() - mtime) > self.max_drift:
356 node.store_timestamp()
360 def get_signature(self, node):
362 Get the appropriate signature for a node.
365 returns - the signature or None if the signature could not
368 This method does not store the signature in the node and
369 in the .sconsign file.
372 if not node.use_signature:
373 # This node type doesn't use a signature (e.g. a
374 # directory) so bail right away.
377 return self.bsig(node)
378 elif not node.exists():
381 return self.csig(node)
383 def current(self, node, newsig):
385 Check if a signature is up to date with respect to a node.
387 node - the node whose signature will be checked
388 newsig - the (presumably current) signature of the file
390 returns - 1 if the file is current with the specified signature,
396 # The node itself has told us whether or not it's
397 # current without checking the signature. The
398 # canonical uses here are a "0" return for a file
399 # that doesn't exist, or a directory.
402 oldtime, oldbsig, oldcsig = node.get_prevsiginfo()
404 if not node.builder and node.get_timestamp() == oldtime:
407 return self.module.current(newsig, oldbsig)
410 default_calc = Calculator()