rebuild in response to changed .c file
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Fri, 21 Sep 2001 11:39:19 +0000 (11:39 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Fri, 21 Sep 2001 11:39:19 +0000 (11:39 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@65 fdb21ef1-2011-0410-befe-b5e4ea1792b1

12 files changed:
src/engine/SCons/Builder.py
src/engine/SCons/Node/FS.py
src/engine/SCons/Node/__init__.py
src/engine/SCons/Sig/MD5.py
src/engine/SCons/Sig/MD5Tests.py
src/engine/SCons/Sig/SigTests.py [new file with mode: 0644]
src/engine/SCons/Sig/TimeStamp.py
src/engine/SCons/Sig/TimeStampTests.py
src/engine/SCons/Sig/__init__.py
src/script/scons.py
test/Program.py
test/option-j.py

index 53e4bcc576c3b3d9d78e056c08434aa4287c8368..0d1d27451165b4be801e2ba50778de827e63bd37 100644 (file)
@@ -10,6 +10,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import os
 import SCons.Node.FS
+import string
 import types
 
 
@@ -41,7 +42,21 @@ class Builder:
        node = self.node_factory(target)
        node.builder_set(self)
        node.env_set(self)
-       node.sources = source   # XXX REACHING INTO ANOTHER OBJECT
+
+        # XXX REACHING INTO ANOTHER OBJECT (this is only temporary):
+        assert type(source) is type("")
+        node.sources = source
+        node.derived = 1
+        sources = string.split(source, " ")
+        sources = filter(lambda x: x, sources)
+        source_nodes = []
+        for source in sources:
+            source_node = self.node_factory(source)
+            source_node.derived = 0
+            source_node.source_nodes = []
+            source_nodes.append(source_node)
+        node.source_nodes = source_nodes
+        
        return node
 
     def execute(self, **kw):
index 9cd0607de0afd99aa45019823a88c0f0b47d66b3..fc7b042c839f9354e0fd97e3ff3633dd3179e37d 100644 (file)
@@ -257,6 +257,14 @@ class File(Node):
     def root(self):
         return self.parent.root()
 
+    def get_contents(self):
+        return open(self.path, "r").read()
+
+    def get_timestamp(self):
+        return os.path.getmtime(self.path)
+
+    def exists(self):
+        return os.path.exists(self.path)
 
 
 default_fs = FS()
index 6da15b6adc15151547ac405bf040a04f6798c844..8e5aab1a2e2aa743482d3fca8a6d0fceac046db7 100644 (file)
@@ -20,3 +20,13 @@ class Node:
 
     def env_set(self, env):
        self.env = env
+
+    def has_signature(self):
+        return hasattr(self, "signature")
+
+    def set_signature(self, signature):
+        self.signature = signature
+
+    def get_signature(self):
+        return self.signature
+
index 10927e0c97d43ccd9d34cd5a5a481551cf3a5129..4c94b1acdb1eefc99630e583a00ca93b9531a029 100644 (file)
@@ -10,7 +10,11 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 import md5
 import string
 
-
+def current(obj, sig):
+    """Return whether a given object is up-to-date with the
+    specified signature.
+    """
+    return obj.get_signature() == sig
 
 def hexdigest(s):
     """Return a signature as a string of hex characters.
@@ -25,46 +29,28 @@ def hexdigest(s):
        r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
     return r
 
-
-
-def _init():
-    pass       # XXX
-
-def _end():
-    pass       # XXX
-
-def current(obj, sig):
-    """Return whether a given object is up-to-date with the
-    specified signature.
+def collect(signatures):
     """
-    return obj.signature() == sig
-
-def set():
-    pass       # XXX
-
-def invalidate():
-    pass       # XXX
+    Collect a list of signatures into an aggregate signature.
 
-def collect(*objects):
-    """Collect signatures from a list of objects, returning the
-    aggregate signature of the list.
+    signatures - a list of signatures
+    returns - the aggregate signature
     """
-    if len(objects) == 1:
-       sig = objects[0].signature()
+    if len(signatures) == 1:
+       return signatures[0]
     else:
-       contents = string.join(map(lambda o: o.signature(), objects), ', ')
-       sig = signature(contents)
-#    if debug:
-#      pass
-    return sig
+        contents = string.join(signatures, ', ')
+       return hexdigest(md5.new(contents).digest())
 
-def signature(contents):
-    """Generate a signature for a byte string.
+def signature(obj):
+    """Generate a signature for an object
     """
-    return hexdigest(md5.new(contents).digest())
+    return hexdigest(md5.new(obj.get_contents()).digest())
 
-def cmdsig():
-    pass       # XXX
+def to_string(signature):
+    """Convert a signature to a string"""
+    return signature
 
-def srcsig():
-    pass       # XXX
+def from_string(string):
+    """Convert a string to a signature"""
+    return string
index 6313475346a544c42bc90305034cbeed1602a7b8..6a8721174765907c09c50fa39c06fc21b4a6db34 100644 (file)
@@ -3,7 +3,8 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 import sys
 import unittest
 
-import SCons.Sig.MD5
+from SCons.Sig.MD5 import current, collect, signature, to_string, from_string
+
 
 
 
@@ -14,61 +15,48 @@ class my_obj:
 
     def __init__(self, value = ""):
        self.value = value
-       self.sig = None
-
-    def signature(self):
-       if not self.sig:
-           self.sig = SCons.Sig.MD5.signature(self.value)
+        
+    def get_signature(self):
+        if not hasattr(self, "sig"):
+           self.sig = signature(self)
        return self.sig
 
-    def current(self, sig):
-       return SCons.Sig.MD5.current(self, sig)
+    def get_contents(self):
+       return self.value
 
 
 
 class MD5TestCase(unittest.TestCase):
 
-    def test__init(self):
-       pass    # XXX
-
-    def test__end(self):
-       pass    # XXX
-
     def test_current(self):
        """Test deciding if an object is up-to-date
 
        Simple comparison of different "signature" values.
        """
-       o111 = my_obj(value = '111')
-       assert not o111.current(SCons.Sig.MD5.signature('110'))
-       assert     o111.current(SCons.Sig.MD5.signature('111'))
-       assert not o111.current(SCons.Sig.MD5.signature('112'))
-
-    def test_set(self):
-       pass    # XXX
-
-    def test_invalidate(self):
-       pass    # XXX
+       obj = my_obj('111')
+       assert not current(obj, signature(my_obj('110')))
+       assert     current(obj, signature(my_obj('111')))
+       assert not current(obj, signature(my_obj('112')))
 
     def test_collect(self):
        """Test collecting a list of signatures into a new signature value
        """
-       o1 = my_obj(value = '111')
-       o2 = my_obj(value = '222')
-       o3 = my_obj(value = '333')
-       assert '698d51a19d8a121ce581499d7b701668' == SCons.Sig.MD5.collect(o1)
-       assert '8980c988edc2c78cc43ccb718c06efd5' == SCons.Sig.MD5.collect(o1, o2)
-       assert '53fd88c84ff8a285eb6e0a687e55b8c7' == SCons.Sig.MD5.collect(o1, o2, o3)
+        s = map(signature, map(my_obj, ('111', '222', '333')))
+        
+        assert '698d51a19d8a121ce581499d7b701668' == collect(s[0:1])
+        assert '8980c988edc2c78cc43ccb718c06efd5' == collect(s[0:2])
+       assert '53fd88c84ff8a285eb6e0a687e55b8c7' == collect(s)
 
     def test_signature(self):
-       pass    # XXX
-
-    def test_cmdsig(self):
-       pass    # XXX
+        """Test generating a signature"""
+       o1 = my_obj(value = '111')
+        assert '698d51a19d8a121ce581499d7b701668' == signature(o1)
 
-    def test_srcsig(self):
-       pass    # XXX
+    def test_to_string(self):
+        assert '698d51a19d8a121ce581499d7b701668' == to_string('698d51a19d8a121ce581499d7b701668')
 
+    def test_from_string(self):
+        assert '698d51a19d8a121ce581499d7b701668' == from_string('698d51a19d8a121ce581499d7b701668')
 
 if __name__ == "__main__":
     suite = unittest.makeSuite(MD5TestCase, 'test_')
diff --git a/src/engine/SCons/Sig/SigTests.py b/src/engine/SCons/Sig/SigTests.py
new file mode 100644 (file)
index 0000000..7fae807
--- /dev/null
@@ -0,0 +1,204 @@
+__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, derived):
+        self.path = path
+        self.contents = contents
+        self.timestamp = timestamp
+        self.derived = derived
+
+    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.derived = file.derived
+        
+    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 has_signature(self):
+        return hasattr(self, "sig")
+
+    def set_signature(self, sig):
+        self.sig = sig
+
+    def get_signature(self):
+        return self.sig
+
+
+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].source_nodes = []
+    nodes[1].source_nodes = [nodes[0]]
+    nodes[2].source_nodes = [nodes[3]]
+    nodes[3].source_nodes = []
+    nodes[4].source_nodes = [nodes[5]]
+    nodes[5].source_nodes = [nodes[6]]
+    nodes[6].source_nodes = [nodes[5]]
+    nodes[7].source_nodes = [nodes[2], nodes[4]]
+    nodes[8].source_nodes = []
+    nodes[9].source_nodes = [nodes[8]]
+    nodes[10].source_nodes = [nodes[9]]
+
+    return nodes
+        
+
+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_delete()
+        
+    def test_initial(self):
+        
+        nodes = create_nodes(self.files)
+        calc = SCons.Sig.Calculator(self.module)
+
+        for node in nodes:
+            self.failUnless(not calc.current(node), "none of the nodes should be current")
+
+        # 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)
+        
+        calc.write(nodes)
+
+    def test_built(self):
+
+        nodes = create_nodes(self.files)
+
+        calc = SCons.Sig.Calculator(self.module)
+        
+        for node in nodes:
+            self.failUnless(calc.current(node), "all of the nodes should be current")
+
+        calc.write(nodes)
+
+    def test_modify(self):
+
+        nodes = create_nodes(self.files)
+
+        #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)
+
+        calc = SCons.Sig.Calculator(self.module)
+
+        self.failUnless(not calc.current(nodes[0]), "modified directly")
+        self.failUnless(not calc.current(nodes[1]), "direct source modified")
+        self.failUnless(calc.current(nodes[2]))
+        self.failUnless(not calc.current(nodes[3]), "modified directly")
+        self.failUnless(calc.current(nodes[4]))
+        self.failUnless(calc.current(nodes[5]))
+        self.failUnless(not calc.current(nodes[6]), "modified directly")
+        self.failUnless(not calc.current(nodes[7]), "indirect source modified")
+        self.failUnless(not calc.current(nodes[8]), "modified directory")
+        self.failUnless(not calc.current(nodes[9]), "direct source modified")
+        self.failUnless(not calc.current(nodes[10]), "indirect source modified")
+
+        calc.write(nodes)
+
+    def test_delete(self):
+        
+        nodes = create_nodes(self.files)
+
+        #simulate the deletion of some files
+        self.files[1].modify(None, 0)
+        self.files[7].modify(None, 0)
+        self.files[9].modify(None, 0)
+        
+        calc = SCons.Sig.Calculator(self.module)
+
+        self.failUnless(calc.current(nodes[0]))
+        self.failUnless(not calc.current(nodes[1]), "deleted")
+        self.failUnless(calc.current(nodes[2]))
+        self.failUnless(calc.current(nodes[3]))
+        self.failUnless(calc.current(nodes[4]))
+        self.failUnless(calc.current(nodes[5]))
+        self.failUnless(calc.current(nodes[6]))
+        self.failUnless(not calc.current(nodes[7]), "deleted")
+        self.failUnless(calc.current(nodes[8]))
+        self.failUnless(not calc.current(nodes[9]), "deleted")
+        self.failUnless(calc.current(nodes[10]),
+                        "current even though it's source was deleted") 
+
+        calc.write(nodes)
+
+class MD5TestCase(unittest.TestCase, SigTestBase):
+    """Test MD5 signatures"""
+
+    module = SCons.Sig.MD5
+
+class TimeStampTestCase(unittest.TestCase, SigTestBase):
+    """Test timestamp signatures"""
+
+    module = SCons.Sig.TimeStamp
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(MD5TestCase())
+    suite.addTest(TimeStampTestCase())
+    return suite
+
+if __name__ == "__main__":
+    runner = unittest.TextTestRunner()
+    result = runner.run(suite())
+    if not result.wasSuccessful():
+        sys.exit(1)
+    
index f66810d5751784a04961ddbd2b4a82ac4c284941..cb2c2023265bec47149f79d4e85bb0e45188bc4f 100644 (file)
@@ -7,43 +7,38 @@ utility.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-def _init():
-    pass       # XXX
-
-def _end():
-    pass       # XXX
-
 def current(obj, sig):
-    """Return whether the object's timestamp is up-to-date.
+    """Return whether the objects timestamp is up-to-date.
     """
-    return obj.signature() >= sig
+    return obj.get_signature() >= sig
 
-def set():
-    pass       # XXX
-
-def invalidate():
-    pass       # XXX
+def collect(signatures):
+    """
+    Collect a list of timestamps, returning
+    the most-recent timestamp from the list 
 
-def collect(*objects):
-    """Collect timestamps from a list of objects, returning
-    the most-recent timestamp from the list.
+    signatures - a list of timestamps
+    returns - the most recent timestamp
     """
-    r = 0
-    for obj in objects:
-       s = obj.signature()
-       if s > r:
-           r = s
-    return r
-
-def signature(contents):
+
+    if len(signatures) == 0:
+        return 0
+    elif len(signatures) == 1:
+        return signatures[0]
+    else:
+        return max(signatures)
+
+def signature(obj):
     """Generate a timestamp.
     """
-    pass       # XXX
-#    return md5.new(contents).hexdigest()      # 2.0
-    return hexdigest(md5.new(contents).digest())
+    return obj.get_timestamp()
+
+def to_string(signature):
+    """Convert a timestamp to a string"""
+    return str(signature)
+
+def from_string(string):
+    """Convert a string to a timestamp"""
+    return int(string)
 
-def cmdsig():
-    pass       # XXX
 
-def srcsig():
-    pass       # XXX
index 2dddff8c923dd2df03bd22ad6cf1e1ae8d99dc03..0c652ab25a2eb02ceaeb87fb51aad7daedd97ef6 100644 (file)
@@ -3,7 +3,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 import sys
 import unittest
 
-import SCons.Sig.TimeStamp
+from SCons.Sig.TimeStamp import current, collect, signature, to_string, from_string
 
 
 
@@ -12,60 +12,47 @@ class my_obj:
     requirements of the TimeStamp class.
     """
 
-    def __init__(self, value = ""):
+    def __init__(self, value = 0):
        self.value = value
 
-    def signature(self):
+    def get_signature(self):
        return self.value
 
+    def get_timestamp(self):
+        return self.value
 
 
 class TimeStampTestCase(unittest.TestCase):
 
-    def test__init(self):
-       pass    # XXX
-
-    def test__init(self):
-       pass    # XXX
-
-    def test__end(self):
-       pass    # XXX
-
     def test_current(self):
        """Test deciding if an object is up-to-date
 
        Simple comparison of different timestamp values.
        """
        o1 = my_obj(value = 111)
-       assert SCons.Sig.TimeStamp.current(o1, 110)
-       assert SCons.Sig.TimeStamp.current(o1, 111)
-       assert not SCons.Sig.TimeStamp.current(o1, 112)
-
-    def test_set(self):
-       pass    # XXX
-
-    def test_invalidate(self):
-       pass    # XXX
+       assert current(o1, 110)
+       assert current(o1, 111)
+       assert not current(o1, 112)
 
     def test_collect(self):
        """Test collecting a list of signatures into a new signature value
        into a new timestamp value.
        """
-       o1 = my_obj(value = 111)
-       o2 = my_obj(value = 222)
-       o3 = my_obj(value = 333)
-       assert 111 == SCons.Sig.TimeStamp.collect(o1)
-       assert 222 == SCons.Sig.TimeStamp.collect(o1, o2)
-       assert 333 == SCons.Sig.TimeStamp.collect(o1, o2, o3)
+        
+       assert 111 == collect((111,))
+       assert 222 == collect((111, 222))
+       assert 333 == collect((333, 222, 111))
 
     def test_signature(self):
-       pass    # XXX
+        """Test generating a signature"""
+        o1 = my_obj(value = 111)
+        assert 111 == signature(o1)
 
-    def test_cmdsig(self):
-       pass    # XXX
+    def test_to_string(self):
+        assert '111' == to_string(111)
 
-    def test_srcsig(self):
-       pass    # XXX
+    def test_from_string(self):
+        assert 111 == from_string('111')
 
 
 if __name__ == "__main__":
index ba485d8857b41fe1cc9c2af33bb0f216b38d7c4a..ea1820e35f5ce26d330527d7ca384a8e1717f94a 100644 (file)
@@ -1,7 +1,193 @@
 """SCons.Sig
 
-The Signature package for the SCons software construction utility.
+The Signature package for the scons software construction utility.
 
 """
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os.path
+import string
+
+class SConsignFile:
+    """
+    Encapsulates reading and writing a .sconsign file.
+    """
+
+    def __init__(self, dir, module):
+        """
+        dir - the directory for the file
+        module - the signature module being used
+        """
+        
+        self.path = os.path.join(dir, '.sconsign')
+        self.entries = {}
+                    
+        try:
+            file = open(self.path, 'rt')
+        except:
+            pass
+        else:
+            for line in file.readlines():
+                filename, rest = map(string.strip, string.split(line, ":"))
+                time, signature = map(string.strip, string.split(rest, " "))
+                self.entries[filename] = (int(time), module.from_string(signature))
+
+    def get(self, filename):
+        """
+        Get the signature for a file
+
+        filename - the filename whose signature will be returned
+        returns - (timestamp, signature)
+        """
+        
+        try:
+            return self.entries[filename]
+        except KeyError:
+            return (0, None)
+
+    def set(self, filename, timestamp, signature, module):
+        """
+        Set the signature for a file
+
+        filename - the filename whose signature will be set
+        timestamp - the timestamp
+        signature - the signature
+        module - the signature module being used
+        """
+        self.entries[filename] = (timestamp, module.to_string(signature))
+
+    def write(self):
+        """
+        Write the .sconsign file to disk.
+        """
+        
+        file = open(self.path, 'wt')
+        for item in self.entries.items():
+            file.write("%s: %d %s\n" % (item[0], item[1][0], item[1][1]))
+
+
+class Calculator:
+    """
+    Encapsulates signature calculations and .sconsign file generating
+    for the build engine.
+    """
+
+    def __init__(self, module):
+        """
+        Initialize the calculator.
+
+        module - the signature module to use for signature calculations
+        """
+        self.module = module
+        self.sig_files = {}
+
+    
+    def collect(self, node, signatures):
+        """
+        Collect the signatures of the node's sources.
+
+        node - the node whose sources will be collected
+        signatures - the dictionary that the signatures will be
+        gathered into.
+        """
+        for source_node in node.source_nodes:
+            if not signatures.has_key(source_node):
+                signature = self.signature(source_node)
+                signatures[source_node] = signature
+                self.collect(source_node, signatures)
+
+    def get_sig_file(self, dir):
+        """
+        Get a sconsign file from the cache, or add it to the cache.
+
+        dir - the dir for the sconsign file
+        returns - the sconsign file
+        """
+        if self.sig_files.has_key(dir):
+            return self.sig_files[dir]
+        else:
+            self.sig_files[dir] = SConsignFile(dir, self.module)
+            return self.sig_files[dir]
+
+    def signature(self, node):
+        """
+        Get the signature for a node.
+
+        node - the node
+        returns - the signature or None if the signature could not
+        be computed.
+
+        This method also stores the signature in the node and
+        in the .sconsign file.
+        """
+
+        if node.has_signature():
+            sig = node.get_signature()
+        elif node.derived:
+            signatures = {}
+            self.collect(node, signatures)
+            signatures = filter(lambda x: not x is None, signatures.values())
+            sig = self.module.collect(signatures)
+        else:
+            if not node.exists():
+                return None
+            
+            # XXX handle nodes that are not under the source root
+            sig = self.module.signature(node)
+
+        node.set_signature(sig)
+
+        dir, filename = os.path.split(node.path)
+        if node.exists():
+            timestamp = node.get_timestamp()
+        else:
+            timestamp = 0
+            
+        self.get_sig_file(dir).set(filename,
+                                   timestamp,
+                                   sig,
+                                   self.module)
+
+        return sig
+
+    def current(self, node):
+        """
+        Check if a node is up to date.
+
+        node - the node whose signature will be checked
+
+        returns - 0 if the signature has changed since the last invocation,
+        and 1 if it hasn't
+        """
+
+        if not node.exists():
+            return 0
+
+        dir, filename = os.path.split(node.path)
+        oldtime, oldsig = self.get_sig_file(dir).get(filename)
+
+        newtime = node.get_timestamp()
+
+        if not node.derived and newtime == oldtime:
+            newsig = oldsig
+        else:
+            newsig = self.signature(node)
+        
+        return newsig == oldsig
+
+    def write(self, nodes):
+        """
+        Write out all of the signature files.
+        
+        nodes - the nodes whose signatures may have changed durring
+        the build
+        """
+
+        # make sure all the signatures have been calculated:
+        for node in nodes:
+            self.signature(node)
+            
+        for sig_file in self.sig_files.values():
+            sig_file.write()
+        
index 51593eb92209931041eced0fd4bf24974f420a0b..a6a7f81a83398ac3c089136aedd4b44ca08f2dc1 100644 (file)
@@ -14,6 +14,8 @@ import traceback
 import SCons.Node.FS
 import SCons.Job
 from SCons.Errors import *
+import SCons.Sig
+import SCons.Sig.MD5
 
 #
 # Modules and classes that we don't use directly in this script, but
@@ -545,14 +547,18 @@ def main():
     if not targets:
        targets = default_targets
 
-    taskmaster = Taskmaster(map(
-                       lambda x: SCons.Node.FS.default_fs.File(x),
-                       targets))
+    calc = SCons.Sig.Calculator(SCons.Sig.MD5)
+    nodes = map(lambda x: SCons.Node.FS.default_fs.File(x), targets)
+    nodes = filter(lambda x, calc=calc: not calc.current(x), nodes)
+    
+    taskmaster = Taskmaster(nodes)
 
     jobs = SCons.Job.Jobs(num_jobs, taskmaster)
     jobs.start()
     jobs.wait()
 
+    calc.write(nodes)
+
 if __name__ == "__main__":
     try:
         main()
index d517808964162bf09a254df3fa6e153db06aa975..4189c4668d76573ff5c55956820450a2b23a88fd 100644 (file)
@@ -3,6 +3,8 @@
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import TestSCons
+import os.path
+import time
 
 #XXX Future:  be able to interpolate
 
@@ -121,4 +123,12 @@ test.run(program = test.workpath('foo2'), stdout = "f2a.c\nf2b.c\nf2c.c\n")
 
 #XXXtest.up_to_date(arguments = '.')
 
+# make sure the programs don't get rebuilt, because nothing changed:
+oldtime1 = os.path.getmtime(test.workpath('foo1'))
+oldtime2 = os.path.getmtime(test.workpath('foo2'))
+time.sleep(1) # introduce a small delay, to make the test valid
+test.run(arguments = 'foo1 foo2')
+test.fail_test(not (oldtime1 == os.path.getmtime(test.workpath('foo1'))))
+test.fail_test(not (oldtime2 == os.path.getmtime(test.workpath('foo2'))))
+
 test.pass_test()
index 90d036d5ea1fd1b5313c94d69592535d366472cc..cc8efa4217508e8d07b0e55ca3e1203842547028 100644 (file)
@@ -36,9 +36,10 @@ env.MyBuild(target = 'f1', source = 'f1.in')
 env.MyBuild(target = 'f2', source = 'f2.in')
 """)
 
-def RunTest(args):
-    test.write('f1.in', 'f1.in')
-    test.write('f2.in', 'f2.in')
+def RunTest(args, extra):
+    """extra is used to make scons rebuild the output file"""
+    test.write('f1.in', 'f1.in'+extra)
+    test.write('f2.in', 'f2.in'+extra)
 
     test.run(arguments = args)
 
@@ -50,13 +51,21 @@ def RunTest(args):
 
     return start2, finish1
 
-start2, finish1 = RunTest('-j 2 f1 f2')
+start2, finish1 = RunTest('-j 2 f1 f2', "first")
 
 # fail if the second file was not started
 # before the first one was finished
 test.fail_test(not (start2 < finish1))
 
-start2, finish1 = RunTest('f1 f2')
+s2, f1 = RunTest('-j 2 f1 f2', "first")
+
+# re-run the test with the same input, fail if we don't
+# get back the same times, which would indicate that
+# SCons rebuilt the files even though nothing changed
+test.fail_test(start2 != s2)
+test.fail_test(finish1 != f1)
+
+start2, finish1 = RunTest('f1 f2', "second")
 
 # fail if the second file was started
 # before the first one was finished