LIBS and LIBPATH work, variable substitution changes.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 15 Nov 2001 03:52:55 +0000 (03:52 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 15 Nov 2001 03:52:55 +0000 (03:52 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@118 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/engine/SCons/Builder.py
src/engine/SCons/BuilderTests.py
src/engine/SCons/Defaults.py
src/engine/SCons/Environment.py
src/engine/SCons/EnvironmentTests.py
src/engine/SCons/Node/NodeTests.py
src/engine/SCons/Node/__init__.py
src/engine/SCons/Util.py
src/engine/SCons/UtilTests.py

index c9556aaa44c36c95527f42402fcddd9f3d935f6f..47199516d20248bc678781ebc5979f81f13cdb73 100644 (file)
@@ -34,7 +34,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 import os
 import os.path
 import SCons.Node.FS
-from SCons.Util import PathList, scons_str2nodes, scons_subst
+from SCons.Util import PathList, scons_str2nodes, scons_subst, scons_subst_list
 import string
 import types
 from UserList import UserList
@@ -363,20 +363,20 @@ class CommandAction(ActionBase):
 
     def execute(self, **kw):
         dict = apply(self.subst_dict, (), kw)
-        cmd_str = scons_subst(self.command, dict, {})
-        for cmd in string.split(cmd_str, '\n'):
-            if print_actions:
-                self.show(cmd)
-            if execute_actions:
-                args = string.split(cmd)
-                try:
-                    ENV = kw['env']['ENV']
-                except:
-                    import SCons.Defaults
-                    ENV = SCons.Defaults.ConstructionEnvironment['ENV']
-                ret = spawn(args[0], args, ENV)
-                if ret:
-                    return ret
+        cmd_list = scons_subst_list(self.command, dict, {})
+        for cmd_line in cmd_list:
+            if len(cmd_line):
+                if print_actions:
+                    self.show(string.join(cmd_line))
+                if execute_actions:
+                    try:
+                        ENV = kw['env']['ENV']
+                    except:
+                        import SCons.Defaults
+                        ENV = SCons.Defaults.ConstructionEnvironment['ENV']
+                    ret = spawn(cmd_line[0], cmd_line, ENV)
+                    if ret:
+                        return ret
         return 0
 
     def get_contents(self, **kw):
index 5668095ed85073b81e32da30e3e54cd16bdd5701..3910323adac8a4c5aa8b78e40e3047f9ec417a76 100644 (file)
@@ -48,10 +48,10 @@ test = TestCmd.TestCmd(workdir = '')
 
 test.write('act.py', """import os, string, sys
 f = open(sys.argv[1], 'w')
-f.write("act.py: " + string.join(sys.argv[2:]) + "\\n")
+f.write("act.py: '" + string.join(sys.argv[2:], "' '") + "'\\n")
 try:
     if sys.argv[3]:
-        f.write("act.py: " + os.environ[sys.argv[3]] + "\\n")
+        f.write("act.py: '" + os.environ[sys.argv[3]] + "'\\n")
 except:
     pass
 f.close()
@@ -61,6 +61,8 @@ sys.exit(0)
 act_py = test.workpath('act.py')
 outfile = test.workpath('outfile')
 
+show_string = None
+
 class Environment:
             def subst(self, s):
                 return s
@@ -127,7 +129,7 @@ class BuilderTestCase(unittest.TestCase):
        r = builder.execute()
        assert r == 0
        c = test.read(outfile, 'r')
-       assert c == "act.py: xyzzy\n", c
+        assert c == "act.py: 'xyzzy'\n", c
 
        cmd2 = r'%s %s %s $TARGET' % (python, act_py, outfile)
 
@@ -135,7 +137,7 @@ class BuilderTestCase(unittest.TestCase):
        r = builder.execute(target = 'foo')
        assert r == 0
        c = test.read(outfile, 'r')
-       assert c == "act.py: foo\n", c
+        assert c == "act.py: 'foo'\n", c
 
        cmd3 = r'%s %s %s ${TARGETS}' % (python, act_py, outfile)
 
@@ -143,7 +145,7 @@ class BuilderTestCase(unittest.TestCase):
        r = builder.execute(target = ['aaa', 'bbb'])
        assert r == 0
        c = test.read(outfile, 'r')
-       assert c == "act.py: aaa bbb\n", c
+        assert c == "act.py: 'aaa' 'bbb'\n", c
 
        cmd4 = r'%s %s %s $SOURCES' % (python, act_py, outfile)
 
@@ -151,7 +153,7 @@ class BuilderTestCase(unittest.TestCase):
        r = builder.execute(source = ['one', 'two'])
        assert r == 0
        c = test.read(outfile, 'r')
-       assert c == "act.py: one two\n", c
+        assert c == "act.py: 'one' 'two'\n", c
 
        cmd4 = r'%s %s %s ${SOURCES[:2]}' % (python, act_py, outfile)
 
@@ -159,7 +161,7 @@ class BuilderTestCase(unittest.TestCase):
        r = builder.execute(source = ['three', 'four', 'five'])
        assert r == 0
        c = test.read(outfile, 'r')
-       assert c == "act.py: three four\n", c
+        assert c == "act.py: 'three' 'four'\n", c
 
        cmd5 = r'%s %s %s $TARGET XYZZY' % (python, act_py, outfile)
 
@@ -167,7 +169,25 @@ class BuilderTestCase(unittest.TestCase):
        r = builder.execute(target = 'out5', env = {'ENV' : {'XYZZY' : 'xyzzy'}})
        assert r == 0
        c = test.read(outfile, 'r')
-       assert c == "act.py: out5 XYZZY\nact.py: xyzzy\n", c
+        assert c == "act.py: 'out5' 'XYZZY'\nact.py: 'xyzzy'\n", c
+
+        cmd7 = '%s %s %s one\n\n%s %s %s two' % (python, act_py, outfile,
+                                                 python, act_py, outfile)
+        expect7 = '%s %s %s one\n%s %s %s two\n' % (python, act_py, outfile,
+                                                    python, act_py, outfile)
+
+        builder = SCons.Builder.Builder(action = cmd7)
+
+        global show_string
+        show_string = ""
+        def my_show(string):
+            global show_string
+            show_string = show_string + string + "\n"
+        builder.action.show = my_show
+
+        r = builder.execute()
+        assert r == 0
+        assert show_string == expect7, show_string
 
        def function1(**kw):
            open(kw['out'], 'w').write("function1\n")
@@ -219,7 +239,7 @@ class BuilderTestCase(unittest.TestCase):
        r = builder.execute(out = outfile)
        assert r.__class__ == class2b
        c = test.read(outfile, 'r')
-       assert c == "act.py: syzygy\nfunction2\nclass2a\nclass2b\n", c
+        assert c == "act.py: 'syzygy'\nfunction2\nclass2a\nclass2b\n", c
 
     def test_get_contents(self):
         """Test returning the signature contents of a Builder
index 114216e5b874eafda952db1aa6831ca469bcbb28..b330dae49d22419037f45589df673f0baef9f3c7 100644 (file)
@@ -90,6 +90,10 @@ if os.name == 'posix':
         'PROGSUFFIX' : '',
         'LIBPREFIX'  : 'lib',
         'LIBSUFFIX'  : '.a',
+        'LIBDIRPREFIX'          : '-L',
+        'LIBDIRSUFFIX'          : '',
+        'LIBLINKPREFIX'         : '-l',
+        'LIBLINKSUFFIX'         : '',
         'ENV'        : { 'PATH' : '/usr/local/bin:/bin:/usr/bin' },
     }
 
@@ -116,6 +120,10 @@ elif os.name == 'nt':
         'PROGSUFFIX' : '.exe',
         'LIBPREFIX'  : '',
         'LIBSUFFIX'  : '.lib',
+        'LIBDIRPREFIX'          : '/L',
+        'LIBDIRSUFFIX'          : '',
+        'LIBLINKPREFIX'         : '',
+        'LIBLINKSUFFIX'         : '$LIBSUFFIX',
         'ENV'        : {
                         'PATH'    : r'C:\Python20;C:\WINNT\system32;C:\WINNT;C:\Program Files\Microsoft Visual Studio\VC98\Bin\;',
                         'PATHEXT' : '.COM;.EXE;.BAT;.CMD',
index 5bf31b40f8a825134b5fce99bf278c401bc981b3..e603b43c5d2c2808533e3e5c837ce5a11e946551 100644 (file)
@@ -37,6 +37,7 @@ import types
 import SCons.Util
 import SCons.Builder
 from SCons.Errors import UserError
+from UserList import UserList
 
 def Command():
     pass       # XXX
@@ -71,6 +72,17 @@ class Environment:
     Environment.
     """
 
+    # See the documentation for the __autogenerate() method
+    # for an explanation of this variable...
+    AUTO_GEN_VARS = ( ( '_LIBFLAGS',
+                        'LIBS',
+                        'LIBLINKPREFIX',
+                        'LIBLINKSUFFIX' ),
+                      ( '_LIBDIRFLAGS',
+                        'LIBPATH',
+                        'LIBDIRPREFIX',
+                        'LIBDIRSUFFIX' ) )
+
     def __init__(self, **kw):
        import SCons.Defaults
        self._dict = copy.deepcopy(SCons.Defaults.ConstructionEnvironment)
@@ -79,6 +91,7 @@ class Environment:
         if kw.has_key('SCANNERS') and type(kw['SCANNERS']) != type([]):
                 kw['SCANNERS'] = [kw['SCANNERS']]
        self._dict.update(copy.deepcopy(kw))
+        self.__autogenerate()
 
        class BuilderWrapper:
            """Wrapper class that allows an environment to
@@ -108,6 +121,48 @@ class Environment:
         for s in self._dict['SCANNERS']:
             setattr(self, s.name, s)
 
+    def __autogenerate(self):
+        """Autogenerate the "interpolated" environment variables.
+        We read a static structure that tells us how.  AUTO_GEN_VARS
+        is a tuple of tuples.  Each inner tuple has four elements,
+        each strings referring to an environment variable, and describing
+        how to autogenerate a particular variable.  The elements are:
+
+        0 - The variable to generate
+        1 - The "source" variable, usually a list
+        2 - The "prefix" variable
+        3 - The "suffix" variable
+
+        The autogenerated variable is a list, consisting of every
+        element of the source list, or a single element if the source
+        is a string, with the prefix and suffix
+        concatenated."""
+
+        for strVarAuto, strSrc, strPref, strSuff, in self.AUTO_GEN_VARS:
+            if self._dict.has_key(strSrc):
+                src_var = self._dict[strSrc]
+                if type(src_var) is types.ListType or \
+                   isinstance(src_var, UserList):
+                    src_var = map(str, src_var)
+                else:
+                    src_var = [ str(src_var), ]
+            else:
+                src_var = []
+
+            try:
+                prefix = str(self._dict[strPref])
+            except KeyError:
+                prefix=''
+
+            try:
+                suffix = str(self._dict[strSuff])
+            except KeyError:
+                suffix =''
+
+            self._dict[strVarAuto] = map(lambda x, suff=suffix, pref=prefix: \
+                                         pref + str(x) + suff,
+                                         src_var)
+
     def __cmp__(self, other):
        return cmp(self._dict, other._dict)
 
@@ -134,6 +189,7 @@ class Environment:
        construction variables and/or values.
        """
        self._dict.update(copy.deepcopy(kw))
+        self.__autogenerate()
 
     def        Depends(self, target, dependency):
        """Explicity specify that 'target's depend on 'dependency'."""
index aff9b01531ca11c17e8d47b5c3cb6c42fe3205dc..1fbdb8c8fa4929a8364476aac0815a67c415f826 100644 (file)
@@ -248,15 +248,26 @@ class EnvironmentTestCase(unittest.TestCase):
        """
        env = Environment(AAA = 'a', BBB = 'b')
        str = env.subst("$AAA ${AAA}A $BBBB $BBB")
-       assert str == "a aA  b", str
+       assert str == "a aA b", str
        env = Environment(AAA = '$BBB', BBB = 'b', BBBA = 'foo')
        str = env.subst("$AAA ${AAA}A ${AAA}B $BBB")
-       assert str == "b foo  b", str
+       assert str == "b foo b", str
        env = Environment(AAA = '$BBB', BBB = '$CCC', CCC = 'c')
        str = env.subst("$AAA ${AAA}A ${AAA}B $BBB")
-       assert str == "c   c", str
-
-
+       assert str == "c c", str
+
+    def test_autogenerate(self):
+        """Test autogenerated environment variables."""
+        env = Environment(LIBS = [ 'foo', 'bar', 'baz' ],
+                          LIBLINKPREFIX = 'foo',
+                          LIBLINKSUFFIX = 'bar')
+        assert len(env.Dictionary('_LIBFLAGS')) == 3, env.Dictionary('_LIBFLAGS')
+        assert env.Dictionary('_LIBFLAGS')[0] == 'foofoobar', \
+               env.Dictionary('_LIBFLAGS')[0]
+        assert env.Dictionary('_LIBFLAGS')[1] == 'foobarbar', \
+               env.Dictionary('_LIBFLAGS')[1]
+        assert env.Dictionary('_LIBFLAGS')[2] == 'foobazbar', \
+               env.Dictionary('_LIBFLAGS')[2]
 
 if __name__ == "__main__":
     suite = unittest.makeSuite(EnvironmentTestCase, 'test_')
index 24ad9558209b904effb53be3b1fc748f4ade3511..373571b48bb83c36a8af227a5e8f0ab1ac478047 100644 (file)
@@ -33,11 +33,15 @@ import SCons.Node
 
 
 built_it = None
+built_target =  None
+built_source =  None
 
 class Builder:
     def execute(self, **kw):
-       global built_it
+        global built_it, built_target, built_source
        built_it = 1
+        built_target = kw['target']
+        built_source = kw['source']
         return 0
     def get_contents(self, env):
         return 7
@@ -70,18 +74,23 @@ class NodeTestCase(unittest.TestCase):
     def test_build(self):
        """Test building a node
        """
+        class MyNode(SCons.Node.Node):
+            def __str__(self):
+                return self.path
        # Make sure it doesn't blow up if no builder is set.
-       node = SCons.Node.Node()
+        node = MyNode()
        node.build()
        assert built_it == None
 
-       node = SCons.Node.Node()
+        node = MyNode()
        node.builder_set(Builder())
        node.env_set(Environment())
-       node.path = "xxx"       # XXX FAKE SUBCLASS ATTRIBUTE
-       node.sources = "yyy"    # XXX FAKE SUBCLASS ATTRIBUTE
+        node.path = "xxx"
+        node.sources = ["yyy", "zzz"]
        node.build()
        assert built_it
+        assert built_target == "xxx", built_target
+        assert built_source == ["yyy", "zzz"], built_source
 
     def test_builder_set(self):
        """Test setting a Node's Builder
index 860061c1379313c11a7dddec70db2160e35628e8..6364bf8d6e4f75e5ca3e2b4aab4097ae4c97ef89 100644 (file)
@@ -71,9 +71,9 @@ class Node:
         """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))
-       stat = self.builder.execute(env = self.env.Dictionary(),
-                                   target = str(self), source = sources_str)
+        sources = map(lambda x: str(x), self.sources)
+        stat = self.builder.execute(env = self.env.Dictionary(),
+                                    target = str(self), source = sources)
        if stat != 0:
            raise BuildError(node = self, stat = stat)
        return stat
index 64930b0a790c721e66f7c8fc1d0483b01dc89362..daeb243dbc28063f875657b46ff6f27fea6aea13 100644 (file)
@@ -36,6 +36,7 @@ import string
 import re
 from UserList import UserList
 import SCons.Node.FS
+import cStringIO
 
 def scons_str2nodes(arg, node_factory=SCons.Node.FS.default_fs.File):
     """This function converts a string or list into a list of Node instances.
@@ -157,36 +158,66 @@ class PathList(UserList):
         # suffix and basepath.
         return self.__class__([ UserList.__getitem__(self, item), ])
 
+_cv = re.compile(r'\$([_a-zA-Z]\w*|{[^}]*})')
+_space_sep = re.compile(r'[\t ]+(?![^{]*})')
 
-
-_tok = r'[_a-zA-Z]\w*'
-_cv = re.compile(r'\$(%s|{%s(\[[-0-9:]*\])?(\.\w+)?})' % (_tok, _tok))
-
-def scons_subst(string, locals, globals):
-    """Recursively interpolates dictionary variables into
-    the specified string, returning the expanded result.
-    Variables are specified by a $ prefix in the string and
-    begin with an initial underscore or alphabetic character
-    followed by any number of underscores or alphanumeric
-    characters.  The construction variable names may be
-    surrounded by curly braces to separate the name from
-    trailing characters.
+def scons_subst_list(strSubst, locals, globals):
     """
+    This function is similar to scons_subst(), but with
+    one important difference.  Instead of returning a single
+    string, this function returns a list of lists.
+    The first (outer) list is a list of lines, where the
+    substituted stirng has been broken along newline characters.
+    The inner lists are lists of command line arguments, i.e.,
+    the argv array that should be passed to a spawn or exec
+    function.
+
+    One important thing this guy does is preserve environment
+    variables that are lists.  For instance, if you have
+    an environment variable that is a Python list (or UserList-
+    derived class) that contains path names with spaces in them,
+    then the entire path will be returned as a single argument.
+    This is the only way to know where the 'split' between arguments
+    is for executing a command line."""
+
     def repl(m, locals=locals, globals=globals):
         key = m.group(1)
         if key[:1] == '{' and key[-1:] == '}':
             key = key[1:-1]
        try:
             e = eval(key, locals, globals)
-            if e is None:
+            if not e:
                 s = ''
+            elif type(e) is types.ListType or \
+                 isinstance(e, UserList):
+                s = string.join(map(str, e), '\0')
             else:
-                s = str(e)
+                s = _space_sep.sub('\0', str(e))
        except NameError:
            s = ''
        return s
     n = 1
+
+    # Tokenize the original string...
+    strSubst = _space_sep.sub('\0', strSubst)
+    
+    # Now, do the substitution
     while n != 0:
-        string, n = _cv.subn(repl, string)
-    return string
+        strSubst, n = _cv.subn(repl, strSubst)
+    # Now parse the whole list into tokens.
+    listLines = string.split(strSubst, '\n')
+    return map(lambda x: filter(lambda y: y, string.split(x, '\0')),
+               listLines)
 
+def scons_subst(strSubst, locals, globals):
+    """Recursively interpolates dictionary variables into
+    the specified string, returning the expanded result.
+    Variables are specified by a $ prefix in the string and
+    begin with an initial underscore or alphabetic character
+    followed by any number of underscores or alphanumeric
+    characters.  The construction variable names may be
+    surrounded by curly braces to separate the name from
+    trailing characters.
+    """
+    cmd_list = scons_subst_list(strSubst, locals, globals)
+    return string.join(map(string.join, cmd_list), '\n')
index 59ce3444e84206382e02c7490a4b389831644197..01d3031146f3e479b2353197397d181e9e87677d 100644 (file)
@@ -30,7 +30,7 @@ import sys
 import unittest
 import SCons.Node
 import SCons.Node.FS
-from SCons.Util import scons_str2nodes, scons_subst, PathList
+from SCons.Util import scons_str2nodes, scons_subst, PathList, scons_subst_list
 
 
 class UtilTestCase(unittest.TestCase):
@@ -125,10 +125,41 @@ class UtilTestCase(unittest.TestCase):
         assert newcom == cvt("test foo")
 
         newcom = scons_subst("test $xxx", loc, {})
-        assert newcom == cvt("test "), newcom
+        assert newcom == cvt("test"), newcom
 
+    def test_subst_list(self):
+        """Testing the scons_subst_list() method..."""
+        loc = {}
+        loc['TARGETS'] = PathList(map(os.path.normpath, [ "./foo/bar.exe",
+                                                          "/bar/baz with spaces.obj",
+                                                          "../foo/baz.obj" ]))
+        loc['TARGET'] = loc['TARGETS'][0]
+        loc['SOURCES'] = PathList(map(os.path.normpath, [ "./foo/blah with spaces.cpp",
+                                                          "/bar/ack.cpp",
+                                                          "../foo/ack.c" ]))
+        loc['xxx'] = None
+        loc['NEWLINE'] = 'before\nafter'
+
+        if os.sep == '/':
+            def cvt(str):
+                return str
+        else:
+            def cvt(str):
+                return string.replace(str, '/', os.sep)
+
+        cmd_list = scons_subst_list("$TARGETS", loc, {})
+        assert cmd_list[0][1] == cvt("/bar/baz with spaces.obj"), cmd_list[0][1]
 
+        cmd_list = scons_subst_list("$SOURCES $NEWLINE $TARGETS", loc, {})
+        assert len(cmd_list) == 2, cmd_list
+        assert cmd_list[0][0] == cvt('foo/blah with spaces.cpp'), cmd_list[0][0]
+        assert cmd_list[1][2] == cvt("/bar/baz with spaces.obj"), cmd_list[1]
 
+        cmd_list = scons_subst_list("$SOURCES$NEWLINE", loc, {})
+        assert len(cmd_list) == 2, cmd_list
+        assert cmd_list[1][0] == 'after', cmd_list[1][0]
+        assert cmd_list[0][2] == cvt('../foo/ack.cbefore'), cmd_list[0][2]
+        
 if __name__ == "__main__":
     suite = unittest.makeSuite(UtilTestCase, 'test_')
     if not unittest.TextTestRunner().run(suite).wasSuccessful():