Commits

Anonymous committed f81cb32

Change node and .sconsign handling to separate build and content signatures.

  • Participants
  • Parent commits ef6cbc6

Comments (0)

Files changed (9)

src/engine/SCons/Node/FS.py

 	"""A FS node's string representation is its path name."""
 	return self.path
 
-    def set_signature(self, sig):
-        SCons.Node.Node.set_signature(self, sig)
-
     def exists(self):
         return os.path.exists(self.path)
 
         """A null "builder" for directories."""
         pass
 
-    def set_signature(self, sig):
+    def set_bsig(self, bsig):
+        """A directory has no signature."""
+        pass
+
+    def set_csig(self, csig):
         """A directory has no signature."""
         pass
 
             return 0
 
     def sconsign(self):
+        """Return the .sconsign file info for this directory,
+        creating it first if necessary."""
         if not self._sconsign:
             #XXX Rework this to get rid of the hard-coding
             import SCons.Sig
             import SCons.Sig.MD5
-            self._sconsign = SCons.Sig.SConsignFile(self.path, SCons.Sig.MD5)
+            self._sconsign = SCons.Sig.SConsignFile(self, SCons.Sig.MD5)
         return self._sconsign
 
 
         else:
             return 0
 
-    def set_signature(self, sig):
-        Entry.set_signature(self, sig)
-        #XXX Rework this to get rid of the hard-coding
-        import SCons.Sig.MD5
-        self.dir.sconsign().set(self.name, self.get_timestamp(), sig, SCons.Sig.MD5)
+    def set_bsig(self, bsig):
+        """Set the build signature for this file, updating the
+        .sconsign entry."""
+        Entry.set_bsig(self, bsig)
+        self.set_sconsign()
 
-    def get_oldentry(self):
+    def set_csig(self, csig):
+        """Set the content signature for this file, updating the
+        .sconsign entry."""
+        Entry.set_csig(self, csig)
+        self.set_sconsign()
+
+    def set_sconsign(self):
+        """Update a file's .sconsign entry with its current info."""
+        self.dir.sconsign().set(self.name, self.get_timestamp(),
+                                self.get_bsig(), self.get_csig())
+
+    def get_prevsiginfo(self):
+        """Fetch the previous signature information from the
+        .sconsign entry."""
         return self.dir.sconsign().get(self.name)
 
 

src/engine/SCons/Node/FSTests.py

         assert e5.path_ == "e3/e5", e5.path_
 	assert e5.dir.path == "e3", e5.dir.path
 
-        #XXX test set_signature()
+        e8 = fs.Entry("e8")
+        assert e8.get_bsig() is None, e8.get_bsig()
+        assert e8.get_csig() is None, e8.get_csig()
+        e8.set_bsig('xxx')
+        e8.set_csig('yyy')
+        assert e8.get_bsig() == 'xxx', e8.get_bsig()
+        assert e8.get_csig() == 'yyy', e8.get_csig()
+
+        f9 = fs.File("f9")
+        assert f9.get_bsig() is None, f9.get_bsig()
+        assert f9.get_csig() is None, f9.get_csig()
+        f9.set_bsig('xxx')
+        f9.set_csig('yyy')
+        assert f9.get_bsig() == 'xxx', f9.get_bsig()
+        assert f9.get_csig() == 'yyy', f9.get_csig()
+
+        d10 = fs.Dir("d10")
+        assert d10.get_bsig() is None, d10.get_bsig()
+        assert d10.get_csig() is None, d10.get_csig()
+        d10.set_bsig('xxx')
+        d10.set_csig('yyy')
+        assert d10.get_bsig() is None, d10.get_bsig()
+        assert d10.get_csig() is None, d10.get_csig()
 
         #XXX test exists()
 
 
         #XXX test get_timestamp()
 
-        #XXX test get_oldentry()
+        #XXX test get_prevsiginfo()
 
 
 

src/engine/SCons/Node/NodeTests.py

 	node.env_set(e)
 	assert node.env == e
 
-    def test_has_signature(self):
-	"""Test whether or not a node has a signature
-	"""
-	node = SCons.Node.Node()
-	assert not node.has_signature()
-	node.set_signature('xxx')
-	assert node.has_signature()
+    def test_set_bsig(self):
+        """Test setting a Node's signature
+        """
+        node = SCons.Node.Node()
+        node.set_bsig('www')
+        assert node.bsig == 'www'
 
-    def test_set_signature(self):
-	"""Test setting a Node's signature
-	"""
-	node = SCons.Node.Node()
-	node.set_signature('yyy')
-        assert node.signature == 'yyy'
+    def test_get_bsig(self):
+        """Test fetching a Node's signature
+        """
+        node = SCons.Node.Node()
+        node.set_bsig('xxx')
+        assert node.get_bsig() == 'xxx'
 
-    def test_get_signature(self):
-	"""Test fetching a Node's signature
-	"""
-	node = SCons.Node.Node()
-	node.set_signature('zzz')
-        assert node.get_signature() == 'zzz'
+    def test_set_csig(self):
+        """Test setting a Node's signature
+        """
+        node = SCons.Node.Node()
+        node.set_csig('yyy')
+        assert node.csig == 'yyy'
+
+    def test_get_csig(self):
+        """Test fetching a Node's signature
+        """
+        node = SCons.Node.Node()
+        node.set_csig('zzz')
+        assert node.get_csig() == 'zzz'
 
     def test_add_dependency(self):
 	"""Test adding dependencies to a Node's list.

src/engine/SCons/Node/__init__.py

 	self.builder = None
 	self.env = None
         self.state = None
+        self.bsig = None
+        self.csig = None
         self.use_signature = 1
 
     def build(self):
+        """Actually build the node.   Return the status from the build."""
 	if not self.builder:
 	    return None
 	sources_str = string.join(map(lambda x: str(x), self.sources))
     def env_set(self, env):
 	self.env = env
 
-    def has_signature(self):
-        return hasattr(self, "signature")
+    def get_bsig(self):
+        """Get the node's build signature (based on the signatures
+        of its dependency files and build information)."""
+        return self.bsig
 
-    def set_signature(self, signature):
-        self.signature = signature
+    def set_bsig(self, bsig):
+        """Set the node's build signature (based on the signatures
+        of its dependency files and build information)."""
+        self.bsig = bsig
 
-    def get_signature(self):
-        return self.signature
+    def get_csig(self):
+        """Get the signature of the node's content."""
+        return self.csig
+
+    def set_csig(self, csig):
+        """Set the signature of the node's content."""
+        self.csig = csig
 
     def add_dependency(self, depend):
 	"""Adds dependencies. The depend argument must be a list."""

src/engine/SCons/Sig/SigTests.py

         self.builder = file.builder
 	self.depends = []
         self.use_signature = 1
+        self.bsig = None
+        self.csig = None
         self.oldtime = 0
-        self.oldsig = 0
+        self.oldbsig = 0
+        self.oldcsig = 0
         
     def get_contents(self):
         # a file that doesn't exist has no contents:
             return 0
         return None
 
-    def has_signature(self):
-        return hasattr(self, "sig")
+    def set_bsig(self, bsig):
+        self.bsig = bsig
 
-    def set_signature(self, sig):
-        self.sig = sig
+    def get_bsig(self):
+        return self.bsig
 
-    def get_signature(self):
-        return self.sig
-
-    def get_oldentry(self):
-        return (self.oldtime, self.oldsig)
+    def get_prevsiginfo(self):
+        return (self.oldtime, self.oldbsig, self.oldcsig)
 
 
 def create_files(test):
 def write(calc, nodes):
     for node in nodes:
         node.oldtime = node.file.timestamp
-        node.oldsig = calc.get_signature(node)
+        node.oldbsig = calc.bsig(node)
+        node.oldcsig = calc.csig(node)
         
 
 class SigTestBase:
             def current(self, newsig, oldsig):
                 return newsig == oldsig
             def signature(self, node):
-                return node.get_signature()
+                return node.get_csig()
 
         class MyNode:
-            def __init__(self, name, sig):
+            def __init__(self, name, bsig, csig):
                 self.name = name
-                self.sig = sig
+                self.bsig = bsig
+                self.csig = csig
                 self.kids = []
                 self.builder = None
                 self.use_signature = 1
             def children(self):
                 return self.kids
-            def has_signature(self):
-                return self.sig != None
-            def get_signature(self):
-                return self.sig
-            def get_oldentry(self):
-                return 0, self.sig
+            def exists(self):
+                return 1
+            def get_bsig(self):
+                return self.bsig
+            def get_csig(self):
+                return self.csig
+            def get_prevsiginfo(self):
+                return 0, self.bsig, self.csig
             def get_timestamp(self):
                 return 1
 
         self.module = MySigModule()
         self.nodeclass = MyNode
         self.test_Calc___init__()
-        self.test_Calc_collect()
+        self.test_Calc_bsig()
         self.test_Calc_get_signature()
         self.test_Calc_current()
 
         self.calc = SCons.Sig.Calculator(self.module)
         assert self.calc.module == self.module
 
-    def test_Calc_collect(self):
-        n1 = self.nodeclass('n1', 11)
-        n2 = self.nodeclass('n2', 22)
-        n3 = self.nodeclass('n3', 33)
+    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.collect(n1) == 55
+        assert self.calc.bsig(n1) == 55
+
+    def test_Calc_bsig(self):
+        n = self.nodeclass('n', 11, 12)
+
+        assert self.calc.csig(n) == 12
 
     def test_Calc_get_signature(self):
         class NE(self.nodeclass):
             def has_signature(self):
                 return None
 
-        n1 = self.nodeclass('n1', 11)
+        n1 = self.nodeclass('n1', 11, 12)
         n1.use_signature = 0
         assert self.calc.get_signature(n1) is None
 
-        n2 = self.nodeclass('n2', 22)
-        assert self.calc.get_signature(n2) == 22
+        n2 = self.nodeclass('n2', 22, 23)
+        assert self.calc.get_signature(n2) == 23
 
-        n3 = self.nodeclass('n3', 33)
-        n4 = self.nodeclass('n4', None)
+        n3 = self.nodeclass('n3', 33, 34)
+        n4 = self.nodeclass('n4', None, None)
         n4.builder = 1
         n4.kids = [n2, n3]
-        assert self.calc.get_signature(n4) == 55
+        assert self.calc.get_signature(n4) == 57
 
-        n5 = NE('n5', 55)
+        n5 = NE('n5', 55, 56)
         assert self.calc.get_signature(n5) is None
 
-        n6 = NN('n6', 66)
-        assert self.calc.get_signature(n6) == 66
+        n6 = NN('n6', 66, 67)
+        assert self.calc.get_signature(n6) == 67
 
     def test_Calc_current(self):
         class N0(self.nodeclass):
             def current(self):
                 return None
 
-        n0 = N0('n0', 11)
+        n0 = N0('n0', 11, 12)
         assert not self.calc.current(n0, 10)
         assert not self.calc.current(n0, 11)
 
-        n1 = N1('n1', 22)
+        n1 = N1('n1', 22, 23)
         assert self.calc.current(n1, 20)
         assert self.calc.current(n1, 22)
 
-        nn = NN('nn', 33)
+        nn = NN('nn', 33, 34)
         assert not self.calc.current(nn, 30)
         assert self.calc.current(nn, 33)
 

src/engine/SCons/Sig/__init__.py

         module - the signature module being used
         """
         
-        self.path = os.path.join(dir, '.sconsign')
+        self.dir = dir
+        self.module = module
+        self.sconsign = os.path.join(dir.path, '.sconsign')
         self.entries = {}
+        self.dirty = None
                     
         try:
-            file = open(self.path, 'rt')
+            file = open(self.sconsign, '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))
+                self.entries[filename] = rest
 
         global sig_files
         sig_files.append(self)
 
     def get(self, filename):
         """
-        Get the signature for a file
+        Get the .sconsign entry for a file
 
         filename - the filename whose signature will be returned
-        returns - (timestamp, signature)
+        returns - (timestamp, bsig, csig)
         """
         
         try:
-            return self.entries[filename]
+            arr = map(string.strip, string.split(self.entries[filename], " "))
         except KeyError:
-            return (0, None)
+            return (None, None, None)
+        try:
+            if arr[1] == '-': bsig = None
+            else:             bsig = self.module.from_string(arr[1])
+        except IndexError:
+            bsig = None
+        try:
+            if arr[2] == '-': csig = None
+            else:             csig = self.module.from_string(arr[2])
+        except IndexError:
+            csig = None
+        return (int(arr[0]), bsig, csig)
 
-    def set(self, filename, timestamp, signature, module):
+    def set(self, filename, timestamp, bsig = None, csig = None):
         """
-        Set the signature for a file
+        Set the .sconsign entry for a file
 
         filename - the filename whose signature will be set
         timestamp - the timestamp
-        signature - the signature
         module - the signature module being used
+        bsig - the file's build signature
+        csig - the file's content signature
         """
-        self.entries[filename] = (timestamp, module.to_string(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
 
     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]))
+        if self.dirty:
+            file = open(self.sconsign, 'wt')
+            keys = self.entries.keys()
+            keys.sort()
+            for name in keys:
+                file.write("%s: %s\n" % (name, self.entries[name]))
 
 
 class Calculator:
         """
         self.module = module
 
-    
-    def collect(self, node):
+    def bsig(self, node):
         """
-        Collect the signatures of a node's sources.
+        Generate a node's build signature, the digested signatures
+        of its dependency files and build information.
 
         node - the node whose sources will be collected
+        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.
         """
+        #XXX If configured, use the content signatures from the
+        #XXX .sconsign file if the timestamps match.
         sigs = map(lambda n,s=self: s.get_signature(n), node.children())
         return self.module.collect(filter(lambda x: not x is None, sigs))
 
+    def csig(self, node):
+        """
+        Generate a node's content signature, the digested signature
+        of its content.
+
+        node - the node
+        returns - the content signature
+        """
+        #XXX If configured, use the content signatures from the
+        #XXX .sconsign file if the timestamps match.
+        return self.module.signature(node)
+
     def get_signature(self, node):
         """
-        Get the signature for a node.
+        Get the appropriate signature for a node.
 
         node - the node
         returns - the signature or None if the signature could not
             # This node type doesn't use a signature (e.g. a
             # directory) so bail right away.
             return None
-        elif node.has_signature():
-            sig = node.get_signature()
         elif node.builder:
-            sig = self.collect(node)
+            return self.bsig(node)
+        elif not node.exists():
+            return None
         else:
-            if not node.exists():
-                return None
-            
-            # XXX handle nodes that are not under the source root
-            sig = self.module.signature(node)
-
-        return sig
+            return self.csig(node)
 
     def current(self, node, newsig):
         """
-        Check if a node is up to date.
+        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 - 0 if the signature has changed since the last invocation,
-        and 1 if it hasn't
+        returns - 1 if the file is current with the specified signature,
+        0 if it isn't
         """
 
         c = node.current()
             # that doesn't exist, or a directory.
             return c
 
-        oldtime, oldsig = node.get_oldentry()
+        oldtime, oldbsig, oldcsig = node.get_prevsiginfo()
 
-        newtime = node.get_timestamp()
-
-        if not node.builder and newtime == oldtime:
-            newsig = oldsig
+        if not node.builder and node.get_timestamp() == oldtime:
+            return 1
         
-        return self.module.current(newsig, oldsig)
+        return self.module.current(newsig, oldbsig)

src/engine/SCons/Taskmaster.py

 
 
 class Task:
-    """Default SCons build engine task."""
+    """Default SCons build engine task.
+    
+    This controls the interaction of the actual building of node
+    and the rest of the engine.
+
+    This is expected to handle all of the normally-customizable
+    aspects of controlling a build, so any given application
+    *should* be able to do what it wants by sub-classing this
+    class and overriding methods as appropriate.  If an application
+    needs to customze something by sub-classing Taskmaster (or
+    some other build engine class), we should first try to migrate
+    that functionality into this class.
+    
+    Note that it's generally a good idea for sub-classes to call
+    these methods explicitly to update state, etc., rather than
+    roll their own interaction with Taskmaster from scratch."""
     def __init__(self, tm, target, top):
         self.tm = tm
         self.target = target
-        self.sig = None
         self.top = top
 
     def execute(self):
             self.target.build()
 
     def get_target(self):
+        """Fetch the target being built or updated by this task.
+        """
         return self.target
 
-    def set_sig(self, sig):
-        self.sig = sig
+    def set_bsig(self, bsig):
+        """Set the task's (*not* the target's)  build signature.
 
-    def set_state(self, state):
+        This will be used later to update the target's build
+        signature if the build succeeds."""
+        self.bsig = bsig
+
+    def set_tstate(self, state):
+        """Set the target node's state."""
         self.target.set_state(state)
 
     def executed(self):
+        """Called when the task has been successfully executed.
+
+        This may have been a do-nothing operation (to preserve
+        build order), so check the node's state before updating
+        things.  Most importantly, this calls back to the
+        Taskmaster to put any node tasks waiting on this one
+        back on the pending list."""
         if self.target.get_state() == SCons.Node.executing:
-            self.set_state(SCons.Node.executed)
+            self.set_tstate(SCons.Node.executed)
+            self.target.set_bsig(self.bsig)
             self.tm.add_pending(self.target)
-            self.target.set_signature(self.sig)
 
     def failed(self):
+        """Default action when a task fails:  stop the build."""
         self.fail_stop()
 
     def fail_stop(self):
-        self.set_state(SCons.Node.failed)
+        """Explicit stop-the-build failure."""
+        self.set_tstate(SCons.Node.failed)
         self.tm.stop()
 
     def fail_continue(self):
+        """Explicit continue-the-build failure.
+
+        This sets failure status on the target node and all of
+        its dependent parent nodes.
+        """
         def get_parents(node): return node.get_parents()
         walker = SCons.Node.Walker(self.target, get_parents)
         while 1:
 
 
 class Calc:
-    def get_signature(self, node):
+    def bsig(self, node):
         """
         """
         return None
 
-    def set_signature(self, node):
-        """
-        """
-        pass
-
     def current(self, node, sig):
         """Default SCons build engine is-it-current function.
     
         self._find_next_ready_node()
 
     def next_task(self):
+        """Return the next task to be executed."""
         if self.ready:
             task = self.ready.pop()
-            
             if not self.ready:
                 self._find_next_ready_node()
             return task
                 # set the signature for non-derived files
                 # here so they don't get recalculated over
                 # and over again:
-                n.set_signature(self.calc.get_signature(n))
+                n.set_csig(self.calc.csig(n))
                 continue
             task = self.tasker(self, n, self.walkers[0].is_done())
             if not n.children_are_executed():
                 n.task = task
                 self.pending = self.pending + 1
                 continue
-            sig = self.calc.get_signature(n)
-            task.set_sig(sig)
-            if self.calc.current(n, sig):
-                task.set_state(SCons.Node.up_to_date)
-            else:
-                task.set_state(SCons.Node.executing)
-
-            self.ready.append(task)
+            self.make_ready(task, n)
             return
             
     def is_blocked(self):
         return not self.ready and self.pending
 
     def stop(self):
+        """Stop the current build completely."""
         self.walkers = []
         self.pending = 0
         self.ready = []
 
     def add_pending(self, node):
-        # add all the pending parents that are now executable to the 'ready'
-        # queue:
+        """Add all the pending parents that are now executable
+        to the 'ready' queue."""
         ready = filter(lambda x: (x.get_state() == SCons.Node.pending
                                   and x.children_are_executed()),
                        node.get_parents())
         for n in ready:
             task = n.task
             delattr(n, "task")
-            sig = self.calc.get_signature(n)
-            task.set_sig(sig)
-            if self.calc.current(n, sig):
-                task.set_state(SCons.Node.up_to_date)
-            else:
-                task.set_state(SCons.Node.executing)
-            self.ready.append(task)
+            self.make_ready(task, n) 
         self.pending = self.pending - len(ready)
 
     def remove_pending(self, node):
+        """Remove a node from the 'ready' queue."""
         if node.get_state() == SCons.Node.pending:
             self.pending = self.pending - 1
+
+    def make_ready(self, task, node):
+        """Common routine that takes a single task+node and makes
+        them available on the 'ready' queue."""
+        bsig = self.calc.bsig(node)
+        task.set_bsig(bsig)
+        if self.calc.current(node, bsig):
+            task.set_tstate(SCons.Node.up_to_date)
+        else:
+            task.set_tstate(SCons.Node.executing)
+        self.ready.append(task)

src/engine/SCons/TaskmasterTests.py

         self.name = name
         self.kids = kids
         self.builder = Node.build
-        self.signature = None
+        self.bsig = None
+        self.csig = None
         self.state = None
         self.parents = []
 
     def set_state(self, state):
         self.state = state
 
-    def set_signature(self, sig):
-        self.signature = sig
+    def set_bsig(self, bsig):
+        self.bsig = bsig
+
+    def set_csig(self, csig):
+        self.csig = csig
   
     def children_are_executed(self):
         return reduce(lambda x,y: ((y.get_state() == SCons.Node.executed
 
 
 
-#class Task(unittest.TestCase):
-#    def test_execute(self):
-#        pass
-#
-#    def test_get_target(self):
-#        pass
-#
-#    def test_set_sig(self):
-#        pass
-#
-#    def test_set_state(self):
-#        pass
-#
-#    def test_up_to_date(self):
-#        pass
-#
-#    def test_executed(self):
-#        pass
-#
-#    def test_failed(self):
-#        pass
-#
-#    def test_fail_stop(self):
-#        pass
-#
-#    def test_fail_continue(self):
-#        pass
-
-    
-
-
-
 class TaskmasterTestCase(unittest.TestCase):
 
     def test_next_task(self):
 
         assert tm.next_task() == None
 
-        global top_node
+        built = "up to date: "
         top_node = n3
 
         class MyCalc(SCons.Taskmaster.Calc):
 
         t = tm.next_task()
         t.execute()
-        print built
         assert built == "n1 up-to-date"
         t.executed()
 
         assert tm.next_task() is None
 
     #def test_add_pending(self):
-    #    passs
+    #    pass
     #
     #def test_remove_pending(self):
-    #    passs
+    #    pass
 
 
 

src/script/scons.py

         global task_class, calc
         task_class = CleanTask
         class CleanCalculator:
-            def get_signature(self, node):
+            def bsig(self, node):
                 return None
-            def set_signature(self, node, sig):
-                pass
+            def csig(self, node):
+                return None
             def current(self, node, sig):
                 return 0
             def write(self):