Strip $(-$) bracketed text from command lines.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 23 Jan 2002 21:54:05 +0000 (21:54 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 23 Jan 2002 21:54:05 +0000 (21:54 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@216 fdb21ef1-2011-0410-befe-b5e4ea1792b1

12 files changed:
doc/man/scons.1
src/CHANGES.txt
src/engine/SCons/Action.py
src/engine/SCons/ActionTests.py
src/engine/SCons/Builder.py
src/engine/SCons/BuilderTests.py
src/engine/SCons/Node/FS.py
src/engine/SCons/Node/__init__.py
src/engine/SCons/Util.py
src/engine/SCons/UtilTests.py
test/CPPPATH.py
test/LIBPATH.py

index ca03f60ebb654090fa92ff3e55f3d9b2be9febcb..dfb96c7d9d1ce0fec5f4b9bbb65e3e3fad48904a 100644 (file)
@@ -1255,6 +1255,48 @@ ${TARGET.filebase}   => file
 ${TARGET.suffix}     => .x
 .EE
 
+.LP
+The special pseudo-variables
+.R $(
+and
+.R $)
+may be used to surround parts of a command line
+that may change
+.I without
+causing a rebuild--that is,
+which are not included in the signature
+of target files built with this command.
+All text between
+.R $(
+and
+.R $)
+will be removed from the command line
+before it is added to file signatures,
+and the
+.R $(
+and
+.R $)
+will be removed before the command is executed.
+For example, the command line:
+
+.ES
+echo Last build occurred $( $TODAY $). > $TARGET
+.EE
+
+.LP
+would execute the command:
+
+.ES
+echo Last build occurred $TODAY. > $TARGET
+.EE
+
+.LP
+but the command signature added to any target files would be:
+
+.ES
+echo Last build occurred  . > $TARGET
+.EE
+
 .\" XXX document how to add user defined scanners. 
 
 .SH EXAMPLES
index 462e06ab8079ee2f0b80fe1170b85217d03fa9cc..44946a0d2f8f63fee89ffcf336fb3ef56607174c 100644 (file)
@@ -38,6 +38,10 @@ RELEASE 0.04 -
   - The C Scanner now always returns a sorted list of dependencies
     so order changes don't cause unnecessary rebuilds.
 
+  - Strip $(-$) bracketed text from command lines.  Use this to
+    surround $_INCDIRS and $_LIBDIRS so we don't rebuild in response
+    to changes to -I or -L options.
+
   From Steve Leblanc:
 
   - Add var=value command-line arguments.
index 38a4c70511007c6e7435810a59f872da28ffd356..26e81a570f8437a9058a7949440015d6fb81b53b 100644 (file)
@@ -31,6 +31,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import os
 import os.path
+import re
 import string
 import sys
 
@@ -238,6 +239,9 @@ class ActionBase:
 
         return dict
 
+_rm = re.compile(r'\$[()]')
+_remove = re.compile(r'\$\(([^\$]|\$[^\(])*?\$\)')
+
 class CommandAction(ActionBase):
     """Class for command-execution actions."""
     def __init__(self, string):
@@ -246,7 +250,7 @@ class CommandAction(ActionBase):
     def execute(self, **kw):
         import SCons.Util
         dict = apply(self.subst_dict, (), kw)
-        cmd_list = SCons.Util.scons_subst_list(self.command, dict, {})
+        cmd_list = SCons.Util.scons_subst_list(self.command, dict, {}, _rm)
         for cmd_line in cmd_list:
             if len(cmd_line):
                 if print_actions:
@@ -262,8 +266,8 @@ class CommandAction(ActionBase):
                         return ret
         return 0
 
-    def get_contents(self, **kw):
-        """Return the signature contents of this action's command line.
+    def _sig_dict(self, kw):
+        """Supply a dictionary for use in computing signatures.
 
         For signature purposes, it doesn't matter what targets or
         sources we use, so long as we use the same ones every time
@@ -272,8 +276,20 @@ class CommandAction(ActionBase):
         """
         kw['target'] = ['__t1__', '__t2__']
         kw['source'] = ['__s1__', '__s2__']
-        dict = apply(self.subst_dict, (), kw)
-        return SCons.Util.scons_subst(self.command, dict, {})
+        return apply(self.subst_dict, (), kw)
+
+    def get_raw_contents(self, **kw):
+        """Return the complete contents of this action's command line.
+        """
+        return SCons.Util.scons_subst(self.command, self._sig_dict(kw), {})
+
+    def get_contents(self, **kw):
+        """Return the signature contents of this action's command line.
+
+        This strips $(-$) and everything in between the string,
+        since those parts don't affect signatures.
+        """
+        return SCons.Util.scons_subst(self.command, self._sig_dict(kw), {}, _remove)
 
 class FunctionAction(ActionBase):
     """Class for Python function actions."""
index a70fd998f80d7dfe02df7f810d8214bede342429..30bf0934e4e6f00e803e2bd2bf7f912cc06273bc 100644 (file)
@@ -125,12 +125,19 @@ class CommandActionTestCase(unittest.TestCase):
         a.execute()
         assert t.executed == 1
 
+    def test_get_raw_contents(self):
+        """Test fetching the contents of a command Action
+        """
+        a = SCons.Action.CommandAction("| $( $foo | $bar $) |")
+        c = a.get_contents(foo = 'FFF', bar = 'BBB')
+        assert c == "| $( FFF | BBB $) |"
+
     def test_get_contents(self):
         """Test fetching the contents of a command Action
         """
-        a = SCons.Action.CommandAction("| $foo | $bar |")
+        a = SCons.Action.CommandAction("| $foo $( | $) $bar |")
         c = a.get_contents(foo = 'FFF', bar = 'BBB')
-        assert c == "| FFF BBB |"
+        assert c == "| FFF BBB |"
 
 class FunctionActionTestCase(unittest.TestCase):
 
index db1e701dd0c3240a17e74545d9388cb7c7e8590e..6fffeee40a246faf2d0d6bd0e99b6c9bd9b1e37e 100644 (file)
@@ -145,6 +145,11 @@ class BuilderBase:
        """
        return apply(self.action.execute, (), kw)
 
+    def get_raw_contents(self, **kw):
+        """Fetch the "contents" of the builder's action.
+        """
+        return apply(self.action.get_raw_contents, (), kw)
+
     def get_contents(self, **kw):
         """Fetch the "contents" of the builder's action
         (for signature calculation).
index e383ee14a3580def8c440d423ef7f1e604709cf2..65aa93471828b7e63174846c0705e5e2694d70f1 100644 (file)
@@ -349,8 +349,11 @@ class BuilderTestCase(unittest.TestCase):
               'INCPREFIX'     : '-I',
               'INCSUFFIX'     : ''}
 
+        contents = apply(b4.get_raw_contents, (), kw)
+        assert contents == "-ll1 -ll2 $( -LlibX $) $( -Ic -Ip $)", contents
+
         contents = apply(b4.get_contents, (), kw)
-        assert contents == "-ll1 -ll2 -LlibX -Ic -Ip", contents
+        assert contents == "-ll1 -ll2", "'%s'" % contents
 
         # SCons.Node.FS has been imported by our import of
         # SCons.Node.Builder.  It's kind of bogus that we don't
@@ -358,8 +361,13 @@ class BuilderTestCase(unittest.TestCase):
         # maybe a little cleaner than tying these tests directly
         # to the other module via a direct import.
         kw['dir'] = SCons.Node.FS.default_fs.Dir('d')
+
+        contents = apply(b4.get_raw_contents, (), kw)
+        expect = os.path.normpath("-ll1 -ll2 $( -Ld/libX $) $( -Id/c -Id/p $)")
+        assert contents == expect, contents + " != " + expect
+
         contents = apply(b4.get_contents, (), kw)
-        expect = os.path.normpath("-ll1 -ll2 -Ld/libX -Id/c -Id/p")
+        expect = os.path.normpath("-ll1 -ll2")
         assert contents == expect, contents + " != " + expect
 
     def test_node_factory(self):
index 66b7314a691e8fa84cb144c7bf22e5de1a81ace0..96ce1481e916b21ea1357c2ddca40cbc46741a5c 100644 (file)
@@ -162,6 +162,7 @@ class FS:
             except KeyError:
                 dir_temp = Dir(path_name, directory)
                 directory.entries[path_norm] = dir_temp
+                directory.add_wkid(dir_temp)
                 directory = dir_temp
         file_name = _my_normcase(path_comp[-1])
         try:
@@ -169,6 +170,7 @@ class FS:
         except KeyError:
             ret = fsclass(path_comp[-1], directory)
             directory.entries[file_name] = ret
+            directory.add_wkid(ret)
         return ret
 
     def __transformPath(self, name, directory):
@@ -431,7 +433,7 @@ class Dir(Entry):
             if s and (not state or s > state):
                 state = s
         import SCons.Node
-        if state == SCons.Node.up_to_date:
+        if state == 0 or state == SCons.Node.up_to_date:
             return 1
         else:
             return 0
index b11027f8b44ea719daa0617ca233ff7d988772c8..f2d379fe39634dd9d729b5ca792db3fdfaa6907e 100644 (file)
@@ -190,6 +190,11 @@ class Node:
 
         if parent not in self.parents: self.parents.append(parent)
 
+    def add_wkid(self, wkid):
+        """Add a node to the list of kids waiting to be evaluated"""
+        if self.wkids != None:
+            self.wkids.append(wkid)
+
     def children(self):
         #XXX Need to remove duplicates from this
         return self.sources \
index a207ffc5f787c0b1c1b66ab32044002d06ae92da..16d2c7622d182d3bfa5eea7836aff83801ef19cb 100644 (file)
@@ -170,7 +170,7 @@ class PathList(UserList.UserList):
 _cv = re.compile(r'\$([_a-zA-Z]\w*|{[^}]*})')
 _space_sep = re.compile(r'[\t ]+(?![^{]*})')
 
-def scons_subst_list(strSubst, locals, globals):
+def scons_subst_list(strSubst, locals, globals, remove=None):
     """
     This function is similar to scons_subst(), but with
     one important difference.  Instead of returning a single
@@ -214,10 +214,12 @@ def scons_subst_list(strSubst, locals, globals):
         strSubst, n = _cv.subn(repl, strSubst)
     # Now parse the whole list into tokens.
     listLines = string.split(strSubst, '\n')
+    if remove:
+        listLines = map(lambda x,re=remove: re.sub('', x), listLines)
     return map(lambda x: filter(lambda y: y, string.split(x, '\0')),
                listLines)
 
-def scons_subst(strSubst, locals, globals):
+def scons_subst(strSubst, locals, globals, remove=None):
     """Recursively interpolates dictionary variables into
     the specified string, returning the expanded result.
     Variables are specified by a $ prefix in the string and
@@ -227,7 +229,7 @@ def scons_subst(strSubst, locals, globals):
     surrounded by curly braces to separate the name from
     trailing characters.
     """
-    cmd_list = scons_subst_list(strSubst, locals, globals)
+    cmd_list = scons_subst_list(strSubst, locals, globals, remove)
     return string.join(map(string.join, cmd_list), '\n')
 
 def find_files(filenames, paths,
@@ -351,6 +353,11 @@ class DirVarInterp(VarInterpolator):
             self.dictInstCache[(dir, fs)] = ret
         return ret
 
+    def generate(self, dict):
+        VarInterpolator.generate(self, dict)
+        if dict[self.dest]:
+            dict[self.dest] = ['$('] + dict[self.dest] + ['$)']
+
 AUTO_GEN_VARS = ( VarInterpolator('_LIBFLAGS',
                                   'LIBS',
                                   'LIBLINKPREFIX',
index e9e763caa77551bb9dd88a5bcf7d08b3332e4af1..ed72ebb677e4c243f569f68eb65c044e998b0c09 100644 (file)
@@ -25,6 +25,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import os
 import os.path
+import re
 import string
 import sys
 import unittest
@@ -127,6 +128,21 @@ class UtilTestCase(unittest.TestCase):
         newcom = scons_subst("test $xxx", loc, {})
         assert newcom == cvt("test"), newcom
 
+        newcom = scons_subst("test $($xxx$)", loc, {})
+        assert newcom == cvt("test $($)"), newcom
+
+        newcom = scons_subst("test $( $xxx $)", loc, {})
+        assert newcom == cvt("test $( $)"), newcom
+
+        newcom = scons_subst("test $($xxx$)", loc, {}, re.compile('\$[()]'))
+        assert newcom == cvt("test"), newcom
+
+        newcom = scons_subst("test $( $xxx $)", loc, {}, re.compile('\$[()]'))
+        assert newcom == cvt("test"), newcom
+
+        newcom = scons_subst("test aXbXcXd", loc, {}, re.compile('X'))
+        assert newcom == cvt("test abcd"), newcom
+
     def test_subst_list(self):
         """Testing the scons_subst_list() method..."""
         loc = {}
@@ -196,18 +212,22 @@ class UtilTestCase(unittest.TestCase):
                 'INCSUFFIX' : 'bar',
                 'FOO'       : 'baz' }
         autogenerate(dict, dir = SCons.Node.FS.default_fs.Dir('/xx'))
-        assert len(dict['_INCFLAGS']) == 5, dict['_INCFLAGS']
-        assert dict['_INCFLAGS'][0] == os.path.normpath('foo/xx/foobar'), \
+        assert len(dict['_INCFLAGS']) == 7, dict['_INCFLAGS']
+        assert dict['_INCFLAGS'][0] == '$(', \
                dict['_INCFLAGS'][0]
-        assert dict['_INCFLAGS'][1] == os.path.normpath('foo/xx/barbar'), \
+        assert dict['_INCFLAGS'][1] == os.path.normpath('foo/xx/foobar'), \
                dict['_INCFLAGS'][1]
-        assert dict['_INCFLAGS'][2] == os.path.normpath('foo/xx/bazbar'), \
+        assert dict['_INCFLAGS'][2] == os.path.normpath('foo/xx/barbar'), \
                dict['_INCFLAGS'][2]
-        assert dict['_INCFLAGS'][3] == os.path.normpath('foo/xx/baz/barbar'), \
+        assert dict['_INCFLAGS'][3] == os.path.normpath('foo/xx/bazbar'), \
                dict['_INCFLAGS'][3]
-        
-        assert dict['_INCFLAGS'][4] == os.path.normpath('fooblatbar'), \
+        assert dict['_INCFLAGS'][4] == os.path.normpath('foo/xx/baz/barbar'), \
                dict['_INCFLAGS'][4]
+        
+        assert dict['_INCFLAGS'][5] == os.path.normpath('fooblatbar'), \
+               dict['_INCFLAGS'][5]
+        assert dict['_INCFLAGS'][6] == '$)', \
+               dict['_INCFLAGS'][6]
 
     def test_render_tree(self):
         class Node:
index af7bdd88f90970f59dca3cbb76c3567c02e8ede4..30439adf249f016e758ed474e6fba0f60671e165 100644 (file)
@@ -161,4 +161,19 @@ test.fail_test(os.path.exists(test.workpath('variant', 'prog.c')))
 
 test.up_to_date(arguments = args)
 
+# Change CPPPATH and make sure we don't rebuild because of it.
+test.write('SConstruct', """
+env = Environment(CPPPATH = ['include'])
+obj = env.Object(target='prog', source='subdir/prog.c')
+env.Program(target='prog', source=obj)
+SConscript('subdir/SConscript', "env")
+
+BuildDir('variant', 'subdir', 0)
+include = Dir('include')
+env = Environment(CPPPATH=[include])
+SConscript('variant/SConscript', "env")
+""")
+
+test.up_to_date(arguments = args)
+
 test.pass_test()
index 7d5a7cc70c9893ead56613849a4f801e325bc61c..a3734256cc97f13cc1559edddff221f0b287caad 100644 (file)
@@ -60,4 +60,16 @@ test.run(arguments = '.')
 test.run(program = test.workpath('prog'),
          stdout = "f1.c\nprog.c\n")
 
+test.up_to_date(arguments = '.')
+
+# Change LIBPATH and make sure we don't rebuild because of it.
+test.write('SConstruct', """
+env = Environment(LIBS = [ 'foo1' ],
+                  LIBPATH = [ './libs', './lib2' ])
+env.Program(target = 'prog', source = 'prog.c')
+env.Library(target = './libs/foo1', source = 'f1.c')
+""")
+
+test.up_to_date(arguments = '.', stderr = None)
+
 test.pass_test()