3 The Signature package for the scons software construction utility.
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__"
45 default_module = TimeStamp
47 default_max_drift = 2*24*60*60
49 #XXX Get rid of the global array so this becomes re-entrant.
56 for sig_file in sig_files:
62 """Objects of this type are pickled to the .sconsign file, so it
63 should only contain simple builtin Python datatypes and no methods.
65 This class is used to store cache information about nodes between
66 scons runs for efficiency, and to store the build signature for
67 nodes so that scons can determine if they are out of date. """
69 # setup the default value for various attributes:
70 # (We make the class variables so the default values won't get pickled
71 # with the instances, which would waste a lot of space)
83 This is the controlling class for the signatures for the collection of
84 entries associated with a specific directory. The actual directory
85 association will be maintained by a subclass that is specific to
86 the underlying storage method. This class provides a common set of
87 methods for fetching and storing the individual bits of information
88 that make up signature entry.
90 def __init__(self, module=None):
92 module - the signature module being used
96 self.module = default_calc.module
102 # A null .sconsign entry. We define this here so that it will
103 # be easy to keep this in sync if/whenever we change the type of
104 # information returned by the get() method, below.
105 null_siginfo = (None, None, None)
107 def get(self, filename):
109 Get the .sconsign entry for a file
111 filename - the filename whose signature will be returned
112 returns - (timestamp, bsig, csig)
114 entry = self.get_entry(filename)
115 return (entry.timestamp, entry.bsig, entry.csig)
117 def get_entry(self, filename):
119 Create an entry for the filename and return it, or if one already exists,
123 return self.entries[filename]
124 except (KeyError, AttributeError):
125 return SConsignEntry()
127 def set_entry(self, filename, entry):
131 self.entries[filename] = entry
134 def set_csig(self, filename, csig):
136 Set the csig .sconsign entry for a file
138 filename - the filename whose signature will be set
139 csig - the file's content signature
142 entry = self.get_entry(filename)
144 self.set_entry(filename, entry)
146 def set_binfo(self, filename, bsig, bkids, bkidsigs, bact, bactsig):
148 Set the build info .sconsign entry for a file
150 filename - the filename whose signature will be set
151 bsig - the file's built signature
154 entry = self.get_entry(filename)
157 entry.bkidsigs = bkidsigs
159 entry.bactsig = bactsig
160 self.set_entry(filename, entry)
162 def set_timestamp(self, filename, timestamp):
164 Set the timestamp .sconsign entry for a file
166 filename - the filename whose signature will be set
167 timestamp - the file's timestamp
170 entry = self.get_entry(filename)
171 entry.timestamp = timestamp
172 self.set_entry(filename, entry)
174 def get_implicit(self, filename):
175 """Fetch the cached implicit dependencies for 'filename'"""
176 entry = self.get_entry(filename)
177 return entry.implicit
179 def set_implicit(self, filename, implicit):
180 """Cache the implicit dependencies for 'filename'."""
181 entry = self.get_entry(filename)
182 if not SCons.Util.is_List(implicit):
183 implicit = [implicit]
184 implicit = map(str, implicit)
185 entry.implicit = implicit
186 self.set_entry(filename, entry)
188 def get_binfo(self, filename):
189 """Fetch the cached implicit dependencies for 'filename'"""
190 entry = self.get_entry(filename)
191 return entry.bsig, entry.bkids, entry.bkidsigs, entry.bact, entry.bactsig
193 class SConsignDB(_SConsign):
195 A _SConsign subclass that reads and writes signature information
196 from a global .sconsign.dbm file.
198 def __init__(self, dir, module=None):
199 _SConsign.__init__(self, module)
205 rawentries = SConsign_db[self.dir.path]
210 self.entries = cPickle.loads(rawentries)
211 if type(self.entries) is not type({}):
214 except KeyboardInterrupt:
217 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
218 "Ignoring corrupt sconsign entry : %s"%self.dir.path)
221 sig_files.append(self)
226 SConsign_db[self.dir.path] = cPickle.dumps(self.entries, 1)
229 except AttributeError:
230 # Not all anydbm modules have sync() methods.
233 class SConsignDir(_SConsign):
234 def __init__(self, fp=None, module=None):
236 fp - file pointer to read entries from
237 module - the signature module being used
239 _SConsign.__init__(self, module)
242 self.entries = cPickle.load(fp)
243 if type(self.entries) is not type({}):
247 class SConsignDirFile(SConsignDir):
249 Encapsulates reading and writing a per-directory .sconsign file.
251 def __init__(self, dir, module=None):
253 dir - the directory for the file
254 module - the signature module being used
258 self.sconsign = os.path.join(dir.path, '.sconsign')
261 fp = open(self.sconsign, 'rb')
266 SConsignDir.__init__(self, fp, module)
267 except KeyboardInterrupt:
270 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
271 "Ignoring corrupt .sconsign file: %s"%self.sconsign)
274 sig_files.append(self)
278 Write the .sconsign file to disk.
280 Try to write to a temporary file first, and rename it if we
281 succeed. If we can't write to the temporary file, it's
282 probably because the directory isn't writable (and if so,
283 how did we build anything in this directory, anyway?), so
284 try to write directly to the .sconsign file as a backup.
285 If we can't rename, try to copy the temporary contents back
286 to the .sconsign file. Either way, always try to remove
287 the temporary file at the end.
290 temp = os.path.join(self.dir.path, '.scons%d' % os.getpid())
292 file = open(temp, 'wb')
296 file = open(self.sconsign, 'wb')
297 fname = self.sconsign
300 cPickle.dump(self.entries, file, 1)
302 if fname != self.sconsign:
304 mode = os.stat(self.sconsign)[0]
305 os.chmod(self.sconsign, 0666)
306 os.unlink(self.sconsign)
310 os.rename(fname, self.sconsign)
312 open(self.sconsign, 'wb').write(open(fname, 'rb').read())
313 os.chmod(self.sconsign, mode)
319 SConsignForDirectory = SConsignDirFile
321 def SConsignFile(name, dbm_module=None):
323 Arrange for all signatures to be stored in a global .sconsign.dbm
327 if SConsign_db is None:
328 if dbm_module is None:
330 dbm_module = SCons.dblite
331 SConsign_db = dbm_module.open(name, "c")
333 global SConsignForDirectory
334 SConsignForDirectory = SConsignDB
338 Encapsulates signature calculations and .sconsign file generating
339 for the build engine.
342 def __init__(self, module=default_module, max_drift=default_max_drift):
344 Initialize the calculator.
346 module - the signature module to use for signature calculations
347 max_drift - the maximum system clock drift used to determine when to
348 cache content signatures. A negative value means to never cache
349 content signatures. (defaults to 2 days)
352 self.max_drift = max_drift
354 def bsig(self, node, cache=None):
356 Generate a node's build signature, the digested signatures
357 of its dependency files and build information.
359 node - the node whose sources will be collected
360 cache - alternate node to use for the signature cache
361 returns - the build signature
363 This no longer handles the recursive descent of the
364 node's children's signatures. We expect that they're
365 already built and updated by someone else, if that's
369 if cache is None: cache = node
371 bsig = cache.get_bsig()
375 children = node.children()
376 bkids = map(str, children)
378 # double check bsig, because the call to children() above may
380 bsig = cache.get_bsig()
384 sigs = map(lambda n, c=self: n.calc_signature(c), children)
386 if node.has_builder():
387 executor = node.get_executor()
389 bactsig = self.module.signature(executor)
395 bsig = self.module.collect(filter(None, sigs))
397 cache.set_binfo(bsig, bkids, sigs, bact, bactsig)
399 # don't store the bsig here, because it isn't accurate until
400 # the node is actually built.
404 def csig(self, node, cache=None):
406 Generate a node's content signature, the digested signature
410 cache - alternate node to use for the signature cache
411 returns - the content signature
414 if cache is None: cache = node
416 csig = cache.get_csig()
420 if self.max_drift >= 0:
421 oldtime, oldbsig, oldcsig = node.get_prevsiginfo()
423 oldtime, oldbsig, oldcsig = _SConsign.null_siginfo
425 mtime = node.get_timestamp()
427 if (oldtime and oldcsig and oldtime == mtime):
428 # use the signature stored in the .sconsign file
430 # Set the csig here so it doesn't get recalculated unnecessarily
431 # and so it's set when the .sconsign file gets written
434 csig = self.module.signature(node)
435 # Set the csig here so it doesn't get recalculated unnecessarily
436 # and so it's set when the .sconsign file gets written
439 if self.max_drift >= 0 and (time.time() - mtime) > self.max_drift:
441 node.store_timestamp()
445 def current(self, node, newsig):
447 Check if a signature is up to date with respect to a node.
449 node - the node whose signature will be checked
450 newsig - the (presumably current) signature of the file
452 returns - 1 if the file is current with the specified signature,
456 if node.always_build:
459 oldtime, oldbsig, oldcsig = node.get_prevsiginfo()
461 if type(newsig) != type(oldbsig):
464 if not node.has_builder() and node.get_timestamp() == oldtime:
467 return self.module.current(newsig, oldbsig)
470 default_calc = Calculator()