__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import unittest
-import TestCmd
import SCons.Sig
-import SCons.Sig.MD5
-import SCons.Sig.TimeStamp
import sys
-
-class DummyFile:
- """A class that simulates a file for testing purposes"""
- def __init__(self, path, contents, timestamp, builder):
- self.path = path
- self.contents = contents
- self.timestamp = timestamp
- self.builder = builder
-
- def modify(self, contents, timestamp):
- self.contents = contents
- self.timestamp = timestamp
-
-class DummyNode:
- """A node workalike for testing purposes"""
-
- def __init__(self, file):
- self.file = file
- self.path = file.path
- self.builder = file.builder
- self.depends = []
- self.ignore = []
- self.use_signature = 1
- self.bsig = None
- self.csig = None
- self.oldtime = 0
- self.oldbsig = 0
- self.oldcsig = 0
- self.always_build = 0
-
- def has_builder(self):
- return self.builder
-
- def get_contents(self):
- # a file that doesn't exist has no contents:
- assert self.exists()
-
- return self.file.contents
-
- def get_timestamp(self):
- # a file that doesn't exist has no timestamp:
- assert self.exists()
-
- return self.file.timestamp
-
- def exists(self):
- return not self.file.contents is None
-
- def cached_exists(self):
- try:
- return self.exists_cache
- except AttributeError:
- self.exists_cache = self.exists()
- return self.exists_cache
-
- def rexists(self):
- return not self.file.contents is None
-
- def children(self):
- return filter(lambda x, i=self.ignore: x not in i,
- self.sources + self.depends)
-
- def all_children(self):
- return self.sources + self.depends
-
- def current(self):
- if not self.exists():
- return 0
- return None
-
- def calc_signature(self, calc):
- if self.has_builder():
- return calc.bsig(self)
- else:
- return calc.csig(self)
-
- def set_binfo(self, bsig, bkids, bkidsigs, bact, bactsig):
- self.bsig = bsig
- self.bkids = bkids
- self.bkidsigs = bkidsigs
- self.bact = bact
- self.bactsig = bactsig
-
- def get_bsig(self):
- return self.bsig
-
- def store_bsig(self):
- pass
-
- def set_csig(self, csig):
- self.csig = csig
-
- def get_csig(self):
- return self.csig
-
- def store_csig(self):
- pass
-
- def get_prevsiginfo(self):
- return (self.oldtime, self.oldbsig, self.oldcsig)
-
- def get_stored_implicit(self):
- return None
-
- def store_timestamp(self):
- pass
-
- def get_executor(self):
- class Adapter:
- def get_contents(self):
- return 111
- def get_timestamp(self):
- return 222
- return Adapter()
-
-
-def create_files(test):
- args = [(test.workpath('f1.c'), 'blah blah', 111, 0), #0
- (test.workpath('f1'), None, 0, 1), #1
- (test.workpath('d1/f1.c'), 'blah blah', 111, 0), #2
- (test.workpath('d1/f1.h'), 'blah blah', 111, 0), #3
- (test.workpath('d1/f2.c'), 'blah blah', 111, 0), #4
- (test.workpath('d1/f3.h'), 'blah blah', 111, 0), #5
- (test.workpath('d1/f4.h'), 'blah blah', 111, 0), #6
- (test.workpath('d1/f1'), None, 0, 1), #7
- (test.workpath('d1/test.c'), 'blah blah', 111, 0),#8
- (test.workpath('d1/test.o'), None, 0, 1), #9
- (test.workpath('d1/test'), None, 0, 1)] #10
-
- files = map(lambda x: apply(DummyFile, x), args)
-
- return files
-
-def create_nodes(files):
- nodes = map(DummyNode, files)
-
- nodes[0].sources = []
- nodes[1].sources = [nodes[0]]
- nodes[2].sources = []
- nodes[3].sources = []
- nodes[4].sources = []
- nodes[5].sources = [nodes[6]]
- nodes[6].sources = [nodes[5]]
- nodes[7].sources = [nodes[2], nodes[4], nodes[3], nodes[5]]
- nodes[8].sources = []
- nodes[9].sources = [nodes[8]]
- nodes[10].sources = [nodes[9]]
-
- return nodes
-
-def current(calc, node):
- return calc.current(node, node.calc_signature(calc))
-
-def write(calc, nodes):
- for node in nodes:
- node.oldtime = node.file.timestamp
- node.oldbsig = calc.bsig(node)
- node.oldcsig = calc.csig(node)
-
-def clear(nodes):
- for node in nodes:
- node.csig = None
- node.bsig = None
-
class SConsignEntryTestCase(unittest.TestCase):
def runTest(self):
-
se = SCons.Sig.SConsignEntry()
assert hasattr(se, 'timestamp'), "no timestamp attribute"
assert hasattr(se, 'bsig'), "no bsig attribute"
assert hasattr(se, 'csig'), "no csig attribute"
assert hasattr(se, 'implicit'), "no implicit attribute"
-class SigTestBase:
-
- def runTest(self):
-
- test = TestCmd.TestCmd(workdir = '')
- test.subdir('d1')
-
- self.files = create_files(test)
- self.test_initial()
- self.test_built()
- self.test_modify()
- self.test_modify_same_time()
-
- def test_initial(self):
-
- nodes = create_nodes(self.files)
- calc = SCons.Sig.Calculator(self.module)
-
- for node in nodes:
- self.failUnless(not current(calc, node),
- "node %s should not be current" % node.path)
-
- # simulate a build:
- self.files[1].modify('built', 222)
- self.files[7].modify('built', 222)
- self.files[9].modify('built', 222)
- self.files[10].modify('built', 222)
-
- def test_built(self):
-
- nodes = create_nodes(self.files)
-
- calc = SCons.Sig.Calculator(self.module)
-
- write(calc, nodes)
-
- for node in nodes:
- self.failUnless(current(calc, node),
- "node %s should be current" % node.path)
-
- def test_modify(self):
-
- nodes = create_nodes(self.files)
-
- calc = SCons.Sig.Calculator(self.module)
-
- write(calc, nodes)
-
- #simulate a modification of some files
- self.files[0].modify('blah blah blah', 333)
- self.files[3].modify('blah blah blah', 333)
- self.files[6].modify('blah blah blah', 333)
- self.files[8].modify('blah blah blah', 333)
-
- clear(nodes)
-
- self.failUnless(not current(calc, nodes[0]), "modified directly")
- self.failUnless(not current(calc, nodes[1]), "direct source modified")
- self.failUnless(current(calc, nodes[2]))
- self.failUnless(not current(calc, nodes[3]), "modified directly")
- self.failUnless(current(calc, nodes[4]))
- self.failUnless(current(calc, nodes[5]))
- self.failUnless(not current(calc, nodes[6]), "modified directly")
- self.failUnless(not current(calc, nodes[7]), "indirect source modified")
- self.failUnless(not current(calc, nodes[8]), "modified directory")
- self.failUnless(not current(calc, nodes[9]), "direct source modified")
- self.failUnless(not current(calc, nodes[10]), "indirect source modified")
-
- def test_modify_same_time(self):
-
- nodes = create_nodes(self.files)
-
- calc = SCons.Sig.Calculator(self.module, 0)
-
- write(calc, nodes)
-
- #simulate a modification of some files without changing the timestamp:
- self.files[0].modify('blah blah blah blah', 333)
- self.files[3].modify('blah blah blah blah', 333)
- self.files[6].modify('blah blah blah blah', 333)
- self.files[8].modify('blah blah blah blah', 333)
-
- clear(nodes)
-
- for node in nodes:
- self.failUnless(current(calc, node),
- "node %s should be current" % node.path)
-
-
-class MD5TestCase(unittest.TestCase, SigTestBase):
- """Test MD5 signatures"""
-
- module = SCons.Sig.MD5
-
-class TimeStampTestCase(unittest.TestCase, SigTestBase):
- """Test timestamp signatures"""
-
- module = SCons.Sig.TimeStamp
-
-class CalcTestCase(unittest.TestCase):
+class CalculatorTestCase(unittest.TestCase):
def runTest(self):
class MySigModule:
- def collect(self, signatures):
- return reduce(lambda x, y: x + y, signatures)
- def current(self, newsig, oldsig):
- return newsig == oldsig
- def signature(self, node):
- return node.get_csig()
-
- class MyNode:
- def __init__(self, name, bsig, csig):
- self.name = name
- self.bsig = bsig
- self.csig = csig
- self.kids = []
- self.ignore = []
- self.builder = None
- self.use_signature = 1
- def has_builder(self):
- return not self.builder is None
- def children(self):
- return filter(lambda x, i=self.ignore: x not in i, self.kids)
- def all_children(self):
- return self.kids
- def exists(self):
- return 1
- def cached_exists(self):
- return 1
- def get_bsig(self):
- return self.bsig
- def set_binfo(self, bsig, bkids, bkidsig, bact, bactsig):
- self.bsig = bsig
- self.bkids = bkids
- self.bkidsigs = bkidsigs
- self.bact = bact
- self.bactsig = bactsig
- def get_csig(self):
- return self.csig
- def set_csig(self, csig):
- self.csig = csig
- def store_csig(self):
- pass
- def store_timestamp(self):
- pass
- def get_prevsiginfo(self):
- return 0, self.bsig, self.csig
- def get_stored_implicit(self):
- return None
- def get_timestamp(self):
- return 1
- def builder_sig_adapter(self):
- class MyAdapter:
- def get_csig(self):
- return 333
- def get_timestamp(self):
- return 444
- return MyAdapter()
-
- self.module = MySigModule()
- self.nodeclass = MyNode
- self.test_Calc___init__()
- self.test_Calc_bsig()
- self.test_Calc_current()
-
- def test_Calc___init__(self):
- self.calc = SCons.Sig.Calculator(self.module)
- assert self.calc.module == self.module
-
- def test_Calc_bsig(self):
- n1 = self.nodeclass('n1', 11, 12)
- n2 = self.nodeclass('n2', 22, 23)
- n3 = self.nodeclass('n3', 33, 34)
- n1.builder = 1
- n1.kids = [n2, n3]
-
- assert self.calc.bsig(n1) == 55
-
- n1.ignore = [n2]
-
- assert self.calc.bsig(n1) == 33
-
- def test_Calc_bsig(self):
- n = self.nodeclass('n', 11, 12)
-
- assert self.calc.csig(n) == 12
-
- def test_Calc_current(self):
- class NN(self.nodeclass):
- always_build = 0
- def current(self):
- return None
-
- nn = NN('nn', 33, 34)
- assert not self.calc.current(nn, 30)
- assert self.calc.current(nn, 33)
- nn.always_build = 1
- assert not self.calc.current(nn, 33)
-
+ pass
+ calc = SCons.Sig.Calculator(MySigModule)
+ assert calc.module == MySigModule
def suite():
suite = unittest.TestSuite()
suite.addTest(SConsignEntryTestCase())
- suite.addTest(MD5TestCase())
- suite.addTest(TimeStampTestCase())
- suite.addTest(CalcTestCase())
+ suite.addTest(CalculatorTestCase())
return suite
if __name__ == "__main__":
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-import cPickle
-import os
-import os.path
-import time
-
-import SCons.Node
-import SCons.Warnings
-
try:
import MD5
default_module = MD5
import TimeStamp
default_module = TimeStamp
+# XXX We should move max_drift into Node/FS.py,
+# since it's really something about files.
default_max_drift = 2*24*60*60
class SConsignEntry:
self.module = module
self.max_drift = max_drift
- 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
- node's children's signatures. We expect that they're
- already built and updated by someone else, if that's
- what's wanted.
- """
-
- if cache is None: cache = node
-
- bsig = cache.get_bsig()
- if bsig is not None:
- return bsig
-
- children = node.children()
- bkids = map(str, children)
-
- # double check bsig, because the call to children() 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():
- executor = node.get_executor()
- bact = str(executor)
- bactsig = self.module.signature(executor)
- sigs.append(bactsig)
- else:
- bact = ""
- bactsig = ""
-
- bsig = self.module.collect(filter(None, sigs))
-
- cache.set_binfo(bsig, bkids, sigs, bact, bactsig)
-
- # 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 cache is None: cache = node
-
- csig = cache.get_csig()
- if csig is not None:
- return csig
-
- if self.max_drift >= 0:
- oldtime, oldbsig, oldcsig = node.get_prevsiginfo()
- else:
- import SCons.SConsign
- oldtime, oldbsig, oldcsig = SCons.SConsign.Base.null_siginfo
-
- mtime = node.get_timestamp()
-
- if (oldtime and oldcsig and oldtime == mtime):
- # use the signature stored in the .sconsign file
- csig = oldcsig
- # 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)
-
- if self.max_drift >= 0 and (time.time() - mtime) > self.max_drift:
- node.store_csig()
- node.store_timestamp()
-
- return csig
-
- def current(self, node, newsig):
- """
- Check if a signature is up to date with respect to a node.
-
- node - the node whose signature will be checked
- newsig - the (presumably current) signature of the file
-
- returns - 1 if the file is current with the specified signature,
- 0 if it isn't
- """
-
- if node.always_build:
- return 0
-
- oldtime, oldbsig, oldcsig = node.get_prevsiginfo()
-
- if type(newsig) != type(oldbsig):
- return 0
-
- if not node.has_builder() and node.get_timestamp() == oldtime:
- return 1
-
- return self.module.current(newsig, oldbsig)
-
-
default_calc = Calculator()
self.tm.executed(self.node)
+ def mark_targets(self, state):
+ for t in self.targets:
+ t.set_state(state)
+
+ def mark_targets_and_side_effects(self, state):
+ for t in self.targets:
+ for side_effect in t.side_effects:
+ side_effect.set_state(state)
+ t.set_state(state)
+
def make_ready_all(self):
"""Mark all targets in a task ready for execution.
visited--the canonical example being the "scons -c" option.
"""
self.out_of_date = self.targets[:]
- state = SCons.Node.executing
- for t in self.targets:
- for side_effect in t.side_effects:
- side_effect.set_state(state)
- t.set_state(state)
+ self.mark_targets_and_side_effects(SCons.Node.executing)
def make_ready_current(self):
"""Mark all targets in a task ready for execution if any target
This is the default behavior for building only what's necessary.
"""
self.out_of_date = []
- calc = self.tm.calc
- if calc:
- for t in self.targets:
- if not t.current(calc):
- self.out_of_date.append(t)
- else:
- for t in self.targets:
- if not t.current(t.calculator()):
- self.out_of_date.append(t)
+ for t in self.targets:
+ if not t.current(t.calculator()):
+ self.out_of_date.append(t)
if self.out_of_date:
- state = SCons.Node.executing
- for t in self.targets:
- for side_effect in t.side_effects:
- side_effect.set_state(state)
- t.set_state(state)
+ self.mark_targets_and_side_effects(SCons.Node.executing)
else:
- state = SCons.Node.up_to_date
- for t in self.targets:
- t.set_state(state)
+ self.mark_targets(SCons.Node.up_to_date)
make_ready = make_ready_current
"""Re-order a list of dependencies (if we need to)."""
return dependencies
-class Calc:
- def bsig(self, node):
- """
- """
- return None
-
- def current(self, node, sig):
- """Default SCons build engine is-it-current function.
-
- This returns "always out of date," so every node is always
- built/visited.
- """
- return 0
class Taskmaster:
"""A generic Taskmaster for handling a bunch of targets.
the base class method, so this class can do its thing.
"""
- def __init__(self, targets=[], tasker=Task, calc=Calc(), order=order):
+ def __init__(self, targets=[], tasker=Task, order=order):
self.targets = targets # top level targets
self.candidates = targets[:] # nodes that might be ready to be executed
self.candidates.reverse()
self.pending = [] # nodes that depend on a currently executing node
self.tasker = tasker
self.ready = None # the next task that is ready to be executed
- self.calc = calc
self.order = order
self.exception_set(None, None)
self.message = None