"""
#
-# Copyright (c) 2001, 2002 Steven Knight
+# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+import cPickle
import os
import os.path
-import string
+import time
+
import SCons.Node
+import SCons.Warnings
+
+try:
+ import MD5
+ default_module = MD5
+except ImportError:
+ import TimeStamp
+ default_module = TimeStamp
+
+default_max_drift = 2*24*60*60
#XXX Get rid of the global array so this becomes re-entrant.
sig_files = []
+# 1 means use build signature for derived source files
+# 0 means use content signature for derived source files
+build_signature = 1
+
def write():
global sig_files
for sig_file in sig_files:
sig_file.write()
+class SConsignEntry:
+
+ """Objects of this type are pickled to the .sconsign file, so it
+ should only contain simple builtin Python datatypes and no methods.
+
+ This class is used to store cache information about nodes between
+ scons runs for efficiency, and to store the build signature for
+ nodes so that scons can determine if they are out of date. """
+
+ # setup the default value for various attributes:
+ # (We make the class variables so the default values won't get pickled
+ # with the instances, which would waste a lot of space)
+ timestamp = None
+ bsig = None
+ csig = None
+ implicit = None
+
class SConsignFile:
"""
Encapsulates reading and writing a .sconsign file.
"""
- def __init__(self, dir, module):
+ def __init__(self, dir, module=None):
"""
dir - the directory for the file
module - the signature module being used
"""
-
+
self.dir = dir
- self.module = module
+
+ if module is None:
+ self.module = default_calc.module
+ else:
+ self.module = module
self.sconsign = os.path.join(dir.path, '.sconsign')
self.entries = {}
- self.dirty = None
-
+ self.dirty = 0
+
try:
- file = open(self.sconsign, 'rt')
+ file = open(self.sconsign, 'rb')
except:
pass
else:
- for line in file.readlines():
- filename, rest = map(string.strip, string.split(line, ":"))
- self.entries[filename] = rest
+ try:
+ self.entries = cPickle.load(file)
+ if type(self.entries) is not type({}):
+ self.entries = {}
+ raise TypeError
+ except:
+ SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
+ "Ignoring corrupt .sconsign file: %s"%self.sconsign)
global sig_files
sig_files.append(self)
filename - the filename whose signature will be returned
returns - (timestamp, bsig, csig)
"""
-
- try:
- arr = map(string.strip, string.split(self.entries[filename], " "))
- except KeyError:
- return (None, None, None)
- try:
- if arr[1] == '-': bsig = None
- else: bsig = self.module.from_string(arr[1])
- except IndexError:
- bsig = None
+ entry = self.get_entry(filename)
+ return (entry.timestamp, entry.bsig, entry.csig)
+
+ def get_entry(self, filename):
+ """
+ Create an entry for the filename and return it, or if one already exists,
+ then return it.
+ """
try:
- if arr[2] == '-': csig = None
- else: csig = self.module.from_string(arr[2])
- except IndexError:
- csig = None
- return (int(arr[0]), bsig, csig)
+ return self.entries[filename]
+ except:
+ return SConsignEntry()
+
+ def set_entry(self, filename, entry):
+ """
+ Set the entry.
+ """
+ self.entries[filename] = entry
+ self.dirty = 1
- def set(self, filename, timestamp, bsig = None, csig = None):
+ def set_csig(self, filename, csig):
"""
- Set the .sconsign entry for a file
+ Set the csig .sconsign entry for a file
filename - the filename whose signature will be set
- timestamp - the timestamp
- module - the signature module being used
- bsig - the file's build signature
csig - the file's content signature
"""
- if bsig is None: bsig = '-'
- else: bsig = self.module.to_string(bsig)
- if csig is None: csig = ''
- else: csig = ' ' + self.module.to_string(csig)
- self.entries[filename] = "%d %s%s" % (timestamp, bsig, csig)
- self.dirty = 1
+
+ entry = self.get_entry(filename)
+ entry.csig = csig
+ self.set_entry(filename, entry)
+
+ def set_bsig(self, filename, bsig):
+ """
+ Set the csig .sconsign entry for a file
+
+ filename - the filename whose signature will be set
+ bsig - the file's built signature
+ """
+
+ entry = self.get_entry(filename)
+ entry.bsig = bsig
+ self.set_entry(filename, entry)
+
+ def set_timestamp(self, filename, timestamp):
+ """
+ Set the csig .sconsign entry for a file
+
+ filename - the filename whose signature will be set
+ timestamp - the file's timestamp
+ """
+
+ entry = self.get_entry(filename)
+ entry.timestamp = timestamp
+ self.set_entry(filename, entry)
+
+ def get_implicit(self, filename):
+ """Fetch the cached implicit dependencies for 'filename'"""
+ entry = self.get_entry(filename)
+ return entry.implicit
+
+ def set_implicit(self, filename, implicit):
+ """Cache the implicit dependencies for 'filename'."""
+ entry = self.get_entry(filename)
+ if SCons.Util.is_String(implicit):
+ implicit = [implicit]
+ implicit = map(str, implicit)
+ entry.implicit = implicit
+ self.set_entry(filename, entry)
def write(self):
"""
if self.dirty:
temp = os.path.join(self.dir.path, '.scons%d' % os.getpid())
try:
- file = open(temp, 'wt')
+ file = open(temp, 'wb')
fname = temp
except:
- file = open(self.sconsign, 'wt')
- fname = self.sconsign
- keys = self.entries.keys()
- keys.sort()
- for name in keys:
- file.write("%s: %s\n" % (name, self.entries[name]))
- file.close
+ try:
+ file = open(self.sconsign, 'wb')
+ fname = self.sconsign
+ except:
+ return
+ cPickle.dump(self.entries, file, 1)
+ file.close()
if fname != self.sconsign:
+ try:
+ mode = os.stat(self.sconsign)[0]
+ os.chmod(self.sconsign, 0666)
+ os.unlink(self.sconsign)
+ except:
+ pass
try:
os.rename(fname, self.sconsign)
except:
open(self.sconsign, 'wb').write(open(fname, 'rb').read())
+ os.chmod(self.sconsign, mode)
try:
os.unlink(temp)
except:
pass
-
class Calculator:
"""
Encapsulates signature calculations and .sconsign file generating
for the build engine.
"""
- def __init__(self, module):
+ def __init__(self, module=default_module, max_drift=default_max_drift):
"""
Initialize the calculator.
module - the signature module to use for signature calculations
+ max_drift - the maximum system clock drift used to determine when to
+ cache content signatures. A negative value means to never cache
+ content signatures. (defaults to 2 days)
"""
self.module = module
+ self.max_drift = max_drift
- def bsig(self, node):
+ def bsig(self, node, cache=None):
"""
Generate a node's build signature, the digested signatures
of its dependency files and build information.
node - the node whose sources will be collected
+ cache - alternate node to use for the signature cache
returns - the build signature
This no longer handles the recursive descent of the
already built and updated by someone else, if that's
what's wanted.
"""
- if not node.use_signature:
- return None
- #XXX If configured, use the content signatures from the
- #XXX .sconsign file if the timestamps match.
- bsig = node.get_bsig()
- if not bsig is None:
+ if cache is None: cache = node
+
+ bsig = cache.get_bsig()
+ if bsig is not None:
return bsig
- # Collect the signatures for ALL the nodes that this
- # node depends on. Just collecting the direct
- # dependants is not good enough, because
- # the signature of a non-derived file does
- # not include the signatures of its psuedo-sources
- # (e.g. the signature for a .c file does not include
- # the signatures of the .h files that it includes).
-
- # However, we do NOT want to walk dependencies of non-
- # derived files, because calling get_signature() on the
- # derived nodes will in turn call bsig() again and do that
- # for us. Hence:
- def walk_non_derived(n, myself=node):
- if not n.builder or n is myself:
- return filter(lambda x, i=myself.ignore: x not in i,
- n.all_children())
- return []
- walker = SCons.Node.Walker(node, walk_non_derived)
- sigs = []
- while 1:
- child = walker.next()
- if child is None: break
- if child is node: continue # skip the node itself
- sigs.append(self.get_signature(child))
-
- if node.builder:
- sigs.append(self.module.signature(node.builder_sig_adapter()))
- return self.module.collect(filter(lambda x: not x is None, sigs))
-
- def csig(self, node):
+ children = node.children()
+
+ # double check bsig, because the call to childre() above may
+ # have set it:
+ bsig = cache.get_bsig()
+ if bsig is not None:
+ return bsig
+
+ sigs = map(lambda n, c=self: n.calc_signature(c), children)
+ if node.has_builder():
+ sigs.append(self.module.signature(node.get_executor()))
+
+ bsig = self.module.collect(filter(lambda x: not x is None, sigs))
+
+ cache.set_bsig(bsig)
+
+ # don't store the bsig here, because it isn't accurate until
+ # the node is actually built.
+
+ return bsig
+
+ def csig(self, node, cache=None):
"""
Generate a node's content signature, the digested signature
of its content.
node - the node
+ cache - alternate node to use for the signature cache
returns - the content signature
"""
- if not node.use_signature:
- return None
- #XXX If configured, use the content signatures from the
- #XXX .sconsign file if the timestamps match.
- csig = node.get_csig()
- if not csig is None:
+
+ if cache is None: cache = node
+
+ csig = cache.get_csig()
+ if csig is not None:
return csig
- return self.module.signature(node)
+ if self.max_drift >= 0:
+ info = node.get_prevsiginfo()
+ else:
+ info = None
- def get_signature(self, node):
- """
- Get the appropriate signature for a node.
+ mtime = node.get_timestamp()
- node - the node
- returns - the signature or None if the signature could not
- be computed.
+ if (info and info[0] and info[2] and info[0] == mtime):
+ # use the signature stored in the .sconsign file
+ csig = info[2]
+ # Set the csig here so it doesn't get recalculated unnecessarily
+ # and so it's set when the .sconsign file gets written
+ cache.set_csig(csig)
+ else:
+ csig = self.module.signature(node)
+ # Set the csig here so it doesn't get recalculated unnecessarily
+ # and so it's set when the .sconsign file gets written
+ cache.set_csig(csig)
- This method does not store the signature in the node and
- in the .sconsign file.
- """
+ if self.max_drift >= 0 and (time.time() - mtime) > self.max_drift:
+ node.store_csig()
+ node.store_timestamp()
- if not node.use_signature:
- # This node type doesn't use a signature (e.g. a
- # directory) so bail right away.
- return None
- elif node.builder:
- return self.bsig(node)
- elif not node.exists():
- return None
- else:
- return self.csig(node)
+ return csig
def current(self, node, newsig):
"""
returns - 1 if the file is current with the specified signature,
0 if it isn't
"""
-
- c = node.current()
- if not c is None:
- # The node itself has told us whether or not it's
- # current without checking the signature. The
- # canonical uses here are a "0" return for a file
- # that doesn't exist, or a directory.
- return c
-
oldtime, oldbsig, oldcsig = node.get_prevsiginfo()
- if not node.builder and node.get_timestamp() == oldtime:
+ if not node.has_builder() and node.get_timestamp() == oldtime:
return 1
-
+
return self.module.current(newsig, oldbsig)
+
+
+default_calc = Calculator()