Commits

Anonymous committed ef4fbe9

Implement special variable substitution.

  • Participants
  • Parent commits a8f0a0c

Comments (0)

Files changed (20)

src/engine/SCons/Builder.py

 
 import os
 import SCons.Node.FS
-import SCons.Util
+from SCons.Util import PathList, scons_str2nodes, scons_varrepl
 import string
 import types
 
 	return cmp(self.__dict__, other.__dict__)
 
     def __call__(self, env, target = None, source = None):
-	tlist = SCons.Util.scons_str2nodes(target, self.node_factory)
-	slist = SCons.Util.scons_str2nodes(source, self.node_factory)
+	tlist = scons_str2nodes(target, self.node_factory)
+	slist = scons_str2nodes(source, self.node_factory)
 	for t in tlist:
 	    t.builder_set(self)
 	    t.env_set(env)
         self.suffix=suffix
 
     def __call__(self, env, target = None, source = None):
-        tlist = SCons.Util.scons_str2nodes(target,
-                                           self.subject.node_factory)
+        tlist = scons_str2nodes(target, self.subject.node_factory)
         tlist_decorated = []
         for tnode in tlist:
             path, fn = os.path.split(tnode.path)
                 self.builder_dict[bld.insuffix] = bld
 
     def __call__(self, env, target = None, source = None):
-        slist = SCons.Util.scons_str2nodes(source, self.node_factory)
+        slist = scons_str2nodes(source, self.node_factory)
         final_sources = []
         for snode in slist:
             path, ext = os.path.splitext(snode.path)
 	self.command = string
 
     def execute(self, **kw):
-	cmd = self.command % kw
+        try:
+            t = kw['target']
+            if type(t) is types.StringType:
+                t = [t]
+            tgt = PathList(map(os.path.normpath, t))
+        except:
+            tgt = PathList()
+        try:
+            s = kw['source']
+            if type(s) is types.StringType:
+                s = [s]
+            src = PathList(map(os.path.normpath, s))
+        except:
+            src = PathList()
+        cmd = scons_varrepl(self.command, tgt, src)
 	if print_actions:
 	    self.show(cmd)
 	ret = 0

src/engine/SCons/BuilderTests.py

     def test__call__(self):
 	"""Test calling a builder to establish source dependencies
 	"""
+	env = Environment()
 	class Node:
 	    def __init__(self, name):
 		self.name = name
 	builder = SCons.Builder.Builder(action = cmd1)
 	r = builder.execute()
 	assert r == 0
-	assert test.read(outfile, 'r') == "act.py: xyzzy\n"
+	c = test.read(outfile, 'r')
+	assert c == "act.py: xyzzy\n", c
+
+	cmd2 = "python %s %s $target" % (act_py, outfile)
+
+	builder = SCons.Builder.Builder(action = cmd2)
+	r = builder.execute(target = 'foo')
+	assert r == 0
+	c = test.read(outfile, 'r')
+	assert c == "act.py: foo\n", c
+
+	cmd3 = "python %s %s ${targets}" % (act_py, outfile)
+
+	builder = SCons.Builder.Builder(action = cmd3)
+	r = builder.execute(target = ['aaa', 'bbb'])
+	assert r == 0
+	c = test.read(outfile, 'r')
+	assert c == "act.py: aaa bbb\n", c
+
+	cmd4 = "python %s %s $sources" % (act_py, outfile)
+
+	builder = SCons.Builder.Builder(action = cmd4)
+	r = builder.execute(source = ['one', 'two'])
+	assert r == 0
+	c = test.read(outfile, 'r')
+	assert c == "act.py: one two\n", c
+
+	cmd4 = "python %s %s ${sources[:2]}" % (act_py, outfile)
+
+	builder = SCons.Builder.Builder(action = cmd4)
+	r = builder.execute(source = ['three', 'four', 'five'])
+	assert r == 0
+	c = test.read(outfile, 'r')
+	assert c == "act.py: three four\n", c
 
 	def function1(kw):
 	    open(kw['out'], 'w').write("function1\n")
 	builder = SCons.Builder.Builder(action = function1)
 	r = builder.execute(out = outfile)
 	assert r == 1
-	assert test.read(outfile, 'r') == "function1\n"
+	c = test.read(outfile, 'r')
+	assert c == "function1\n", c
 
 	class class1a:
 	    def __init__(self, kw):
 	builder = SCons.Builder.Builder(action = class1a)
 	r = builder.execute(out = outfile)
 	assert r.__class__ == class1a
-	assert test.read(outfile, 'r') == "class1a\n"
+	c = test.read(outfile, 'r')
+	assert c == "class1a\n", c
 
 	class class1b:
 	    def __call__(self, kw):
 	builder = SCons.Builder.Builder(action = class1b())
 	r = builder.execute(out = outfile)
 	assert r == 2
-	assert test.read(outfile, 'r') == "class1b\n"
+	c = test.read(outfile, 'r')
+	assert c == "class1b\n", c
 
 	cmd2 = "python %s %s syzygy" % (act_py, outfile)
 
 	builder = SCons.Builder.Builder(action = [cmd2, function2, class2a(), class2b])
 	r = builder.execute(out = outfile)
 	assert r.__class__ == class2b
-	assert test.read(outfile, 'r') == "act.py: syzygy\nfunction2\nclass2a\nclass2b\n"
+	c = test.read(outfile, 'r')
+	assert c == "act.py: syzygy\nfunction2\nclass2a\nclass2b\n", c
 
     def test_insuffix(self):
 	"""Test Builder creation with a specified input suffix

src/engine/SCons/Defaults.py

 
 
 Object = SCons.Builder.Builder(name = 'Object',
-                               action = 'cc -c -o %(target)s %(source)s',
+                               action = 'cc -c -o $target $sources',
                                input_suffix='.c',
                                output_suffix='.o')
 Program = SCons.Builder.MultiStepBuilder(name = 'Program',
-                                         action = 'cc -o %(target)s %(source)s',
+                                         action = 'cc -o $target $sources',
                                          builders = [ Object ])
 Library = SCons.Builder.MultiStepBuilder(name = 'Library',
-                                         action = 'ar r %(target)s %(source)s\nranlib %(target)s',
+                                         action = 'ar r $target $sources\nranlib $target',
                                          builders = [ Object ])
-
+  
 Library = SCons.Builder.TargetNamingBuilder(builder = Library,
                                             prefix='lib',
                                             suffix='.a')

src/engine/SCons/Util.py

 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 
+import os.path
 import types
 import string
+import re
+from UserList import UserList
 import SCons.Node.FS
 
 def scons_str2nodes(arg, node_factory=SCons.Node.FS.default_fs.File):
 	    nodes.append(v)
 
     return nodes
+
+
+class PathList(UserList):
+    """This class emulates the behavior of a list, but also implements
+    the special "path dissection" attributes we can use to find
+    suffixes, base names, etc. of the paths in the list.
+
+    One other special attribute of this class is that, by
+    overriding the __str__ and __repr__ methods, this class
+    represents itself as a space-concatenated string of
+    the list elements, as in:
+
+    >>> pl=PathList(["/foo/bar.txt", "/baz/foo.txt"])
+    >>> pl
+    '/foo/bar.txt /baz/foo.txt'
+    >>> pl.base
+    'bar foo'
+    """
+    def __init__(self, seq = []):
+        UserList.__init__(self, seq)
+
+    def __getattr__(self, name):
+        # This is how we implement the "special" attributes
+        # such as base, suffix, basepath, etc.
+        try:
+            return self.dictSpecialAttrs[name](self)
+        except KeyError:
+            raise AttributeError, 'PathList has no attribute: %s' % name
+
+    def __splitPath(self, split_func=os.path.split):
+        """This method calls the supplied split_func on each element
+        in the contained list.  We expect split_func to return a
+        2-tuple, usually representing two elements of a split file path,
+        such as those returned by os.path.split().
+
+        We return a 2-tuple of lists, each equal in length to the contained
+        list.  The first list represents all the elements from the
+        first part of the split operation, the second represents
+        all elements from the second part."""
+        list1 = []
+        list2 = []
+        for strPath in self.data:
+            first_part, second_part = split_func(strPath)
+            list1.append(first_part)
+            list2.append(second_part)
+        return (self.__class__(list1),
+                self.__class__(list2))
+
+    def __getBasePath(self):
+        """Return the file's directory and file name, with the
+        suffix stripped."""
+        return self.__splitPath(os.path.splitext)[0]
+
+    def __getSuffix(self):
+        """Return the file's suffix."""
+        return self.__splitPath(os.path.splitext)[1]
+
+    def __getFileName(self):
+        """Return the file's name without the path."""
+        return self.__splitPath()[1]
+
+    def __getDir(self):
+        """Return the file's path."""
+        return self.__splitPath()[0]
+
+    def __getBase(self):
+        """Return the file name with path and suffix stripped."""
+        return self.__getFileName().__splitPath(os.path.splitext)[0]
+
+    dictSpecialAttrs = { "file" : __getFileName,
+                         "base" : __getBasePath,
+                         "filebase" : __getBase,
+                         "dir" : __getDir,
+                         "suffix" : __getSuffix }
+    
+    def __str__(self):
+        return string.join(self.data)
+
+    def __repr__(self):
+        return repr(string.join(self.data))
+
+    def __getitem__(self, item):
+        # We must do this to ensure that single items returned
+        # by index access have the special attributes such as
+        # suffix and basepath.
+        return self.__class__([ UserList.__getitem__(self, item), ])
+
+
+__tcv = re.compile(r'\$(\{?targets?(\[[0-9:]+\])?(\.[a-z]+)?\}?)')
+__scv = re.compile(r'\$(\{?sources(\[[0-9:]+\])?(\.[a-z]+)?\}?)')
+def scons_varrepl(command, targets, sources):
+    """This routine handles variable interpolation for the $targets and
+    $sources variables in the 'command' argument. The targets and sources
+    given in the other arguements must be lists containing 'Node's."""
+
+    def repl(m, targets=targets, sources=sources):
+	globals = {}
+        key = m.group(1)
+        if key[0] == '{':
+            if key[-1] == '}':
+                key = key[1:-1]
+            else:
+                raise SyntaxError, "Bad regular expression"
+
+        if key[:6] == 'target':
+	    globals['targets'] = targets
+	    globals['target'] = targets[0]
+	if key[:7] == 'sources':
+	    globals['sources'] = sources
+	if globals:
+            return str(eval(key, globals ))
+
+    command = __tcv.sub(repl, command)
+    command = __scv.sub(repl, command)
+    return command
+
+

src/engine/SCons/UtilTests.py

 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import sys
+import os.path
 import unittest
 import SCons.Node
 import SCons.Node.FS
-from SCons.Util import scons_str2nodes
+from SCons.Util import scons_str2nodes, scons_varrepl, PathList
+
 
 class UtilTestCase(unittest.TestCase):
     def test_str2nodes(self):
 	    pass
 	node = scons_str2nodes(OtherNode())
 
+
+    def test_varrepl(self):
+	"""Test the varrepl function."""
+        targets = PathList(map(os.path.normpath, [ "./foo/bar.exe",
+                                                   "/bar/baz.obj",
+                                                   "../foo/baz.obj" ]))
+        sources = PathList(map(os.path.normpath, [ "./foo/blah.cpp",
+                                                   "/bar/ack.cpp",
+                                                   "../foo/ack.c" ]))
+
+        newcom = scons_varrepl("test $targets $sources", targets, sources)
+	assert newcom == "test foo/bar.exe /bar/baz.obj ../foo/baz.obj foo/blah.cpp /bar/ack.cpp ../foo/ack.c"
+
+        newcom = scons_varrepl("test $targets[:] $sources[0]", targets, sources)
+	assert newcom == "test foo/bar.exe /bar/baz.obj ../foo/baz.obj foo/blah.cpp"
+
+        newcom = scons_varrepl("test ${targets[1:]}v", targets, sources)
+	assert newcom == "test /bar/baz.obj ../foo/baz.objv"
+
+        newcom = scons_varrepl("test $target", targets, sources)
+	assert newcom == "test foo/bar.exe"
+
+        newcom = scons_varrepl("test $target$source[0]", targets, sources)
+	assert newcom == "test foo/bar.exe$source[0]"
+
+        newcom = scons_varrepl("test ${target.file}", targets, sources)
+	assert newcom == "test bar.exe"
+
+        newcom = scons_varrepl("test ${target.filebase}", targets, sources)
+	assert newcom == "test bar"
+
+        newcom = scons_varrepl("test ${target.suffix}", targets, sources)
+	assert newcom == "test .exe"
+
+        newcom = scons_varrepl("test ${target.base}", targets, sources)
+	assert newcom == "test foo/bar"
+
+        newcom = scons_varrepl("test ${target.dir}", targets, sources)
+	assert newcom == "test foo"
+
+
+
 if __name__ == "__main__":
     suite = unittest.makeSuite(UtilTestCase, 'test_')
     if not unittest.TextTestRunner().run(suite).wasSuccessful():
 test.write('SConstruct', """
 env = Environment()
 env.Command(target = 'f1.out', source = 'f1.in',
-		action = "python build.py %(target)s %(source)s")
+		action = "python build.py $target $sources")
 env.Command(target = 'f2.out', source = 'f2.in',
-		action = "python build.py temp2 %(source)s\\npython build.py %(target)s temp2")
+		action = "python build.py temp2 $sources\\npython build.py $target temp2")
 env.Command(target = 'f3.out', source = 'f3.in',
-		action = ["python build.py temp3 %(source)s",
-			  "python build.py %(target)s temp3"])
+		action = ["python build.py temp3 $sources",
+			  "python build.py $target temp3"])
 # Eventually, add ability to do execute Python code.
 """)
 
 """)
 
 test.write(['one', 'SConstruct'], """
-B = Builder(name = 'B', action = "python ../build.py %(target)s %(source)s")
+B = Builder(name = 'B', action = "python ../build.py $target $sources")
 env = Environment(BUILDERS = [B])
 env.B(target = 'foo.out', source = 'foo.in')
 env.B(target = 'bar.out', source = 'bar.in')
 """)
 
 test.write(['two', 'SConstruct'], """
-B = Builder(name = 'B', action = "python ../build.py %(target)s %(source)s")
+B = Builder(name = 'B', action = "python ../build.py $target $sources")
 env = Environment(BUILDERS = [B])
 env.B(target = 'foo.out', source = 'foo.in')
 env.B(target = 'bar.out', source = 'bar.in')
 """)
 
 test.write(['three', 'SConstruct'], """
-B = Builder(name = 'B', action = "python ../build.py %(target)s %(source)s")
+B = Builder(name = 'B', action = "python ../build.py $target $sources")
 env = Environment(BUILDERS = [B])
 env.B(target = 'foo.out', source = 'foo.in')
 env.B(target = 'bar.out', source = 'bar.in')
 
 test.write('SConstruct', """
 Foo = Builder(name = "Foo",
-	  action = "python build.py %(target)s %(source)s subdir/foo.dep")
+	  action = "python build.py $target $sources subdir/foo.dep")
 Bar = Builder(name = "Bar",
-	  action = "python build.py %(target)s %(source)s subdir/bar.dep")
+	  action = "python build.py $target $sources subdir/bar.dep")
 env = Environment(BUILDERS = [Foo, Bar])
 env.Depends(target = ['f1.out', 'f2.out'], dependency = 'subdir/foo.dep')
 env.Depends(target = 'f3.out', dependency = 'subdir/bar.dep')
 import os
 bin1_path = r'%s' + os.pathsep + os.environ['PATH']
 bin2_path = r'%s' + os.pathsep + os.environ['PATH']
-Bld = Builder(name = 'Bld', action = "build.py %%(target)s %%(source)s")
+Bld = Builder(name = 'Bld', action = "build.py $target $sources")
 bin1 = Environment(ENV = {'PATH' : bin1_path}, BUILDERS = [Bld])
 bin2 = Environment(ENV = {'PATH' : bin2_path}, BUILDERS = [Bld])
 bin1.Bld(target = 'bin1.out', source = 'input')
 #XXX Need to switch TestBld to Program() when LIBS variable is working.
 test.write('SConstruct', """
 TestBld = Builder(name='TestBld',
-                  action='cc -o %(target)s %(source)s -L./ -lfoo1 -lfoo2 -lfoo3')
+                  action='cc -o $target $sources -L./ -lfoo1 -lfoo2 -lfoo3')
 env = Environment(BUILDERS=[ TestBld, Library ])
 env.Library(target = 'foo1', source = 'f1.c')
 env.Library(target = 'foo2', source = 'f2a.c f2b.c f2c.c')

test/builderrors.py

 """)
 
 test.write(['one', 'SConstruct'], """
-B0 = Builder(name = 'B0', action = "python ../build.py 0 %(target)s %(source)s")
-B1 = Builder(name = 'B1', action = "python ../build.py 1 %(target)s %(source)s")
+B0 = Builder(name = 'B0', action = "python ../build.py 0 $target $sources")
+B1 = Builder(name = 'B1', action = "python ../build.py 1 $target $sources")
 env = Environment(BUILDERS = [B0, B1])
 env.B1(target = 'f1.out', source = 'f1.in')
 env.B0(target = 'f2.out', source = 'f2.in')
 test.fail_test(os.path.exists(test.workpath('f3.out')))
 
 test.write(['two', 'SConstruct'], """
-B0 = Builder(name = 'B0', action = "python ../build.py 0 %(target)s %(source)s")
-B1 = Builder(name = 'B1', action = "python ../build.py 1 %(target)s %(source)s")
+B0 = Builder(name = 'B0', action = "python ../build.py 0 $target $sources")
+B1 = Builder(name = 'B1', action = "python ../build.py 1 $target $sources")
 env = Environment(BUILDERS = [B0, B1])
 env.B0(target = 'f1.out', source = 'f1.in')
 env.B1(target = 'f2.out', source = 'f2.in')
 test.fail_test(os.path.exists(test.workpath('f3.out')))
 
 test.write(['three', 'SConstruct'], """
-B0 = Builder(name = 'B0', action = "python ../build.py 0 %(target)s %(source)s")
-B1 = Builder(name = 'B1', action = "python ../build.py 1 %(target)s %(source)s")
+B0 = Builder(name = 'B0', action = "python ../build.py 0 $target $sources")
+B1 = Builder(name = 'B1', action = "python ../build.py 1 $target $sources")
 env = Environment(BUILDERS = [B0, B1])
 env.B0(target = 'f1.out', source = 'f1.in')
 env.B0(target = 'f2.out', source = 'f2.in')

test/multiline.py

 """)
 
 test.write('SConstruct', """
-B1 = Builder(name = 'B1', action = ["python build.py .temp %(source)s",
-				    "python build.py %(target)s .temp"])
-B2 = Builder(name = 'B2', action = "python build.py .temp %(source)s\\npython build.py %(target)s .temp")
+B1 = Builder(name = 'B1', action = ["python build.py .temp $sources",
+				    "python build.py $targets .temp"])
+B2 = Builder(name = 'B2', action = "python build.py .temp $sources\\npython build.py $targets .temp")
 env = Environment(BUILDERS = [B1, B2])
 env.B1(target = 'foo1.out', source = 'foo1.in')
 env.B2(target = 'foo2.out', source = 'foo2.in')
 
 test.write('SConstruct', """
 MyBuild = Builder(name = "MyBuild",
-		  action = "python build.py %(target)s")
+		  action = "python build.py $targets")
 env = Environment(BUILDERS = [MyBuild])
 env.MyBuild(target = '-f1.out', source = 'f1.in')
 env.MyBuild(target = '-f2.out', source = 'f2.in')
 """)
 
 test.write('SConstruct', """
-B = Builder(name = 'B', action = "python build.py %(target)s %(source)s")
+B = Builder(name = 'B', action = "python build.py $targets $sources")
 env = Environment(BUILDERS = [B])
 env.B(target = 'foo1.out', source = 'foo1.in')
 env.B(target = 'foo2.out', source = 'foo2.in')
 """)
 
 test.write('SConstruct', """
-Succeed = Builder(name = "Succeed", action = "python succeed.py %(target)s")
-Fail = Builder(name = "Fail", action = "python fail.py %(target)s")
+Succeed = Builder(name = "Succeed", action = "python succeed.py $targets")
+Fail = Builder(name = "Fail", action = "python fail.py $targets")
 env = Environment(BUILDERS = [Succeed, Fail])
 env.Fail(target = 'aaa.1', source = 'aaa.in')
 env.Succeed(target = 'aaa.out', source = 'aaa.1')
 
 test.write('SConstruct', """
 MyBuild = Builder(name = "MyBuild",
-		  action = "python build.py %(target)s")
+		  action = "python build.py $targets")
 env = Environment(BUILDERS = [MyBuild])
 env.MyBuild(target = 'f1', source = 'f1.in')
 env.MyBuild(target = 'f2', source = 'f2.in')
 """)
 
 test.write('SConstruct', """
-Succeed = Builder(name = "Succeed", action = "python succeed.py %(target)s")
-Fail = Builder(name = "Fail", action = "python fail.py %(target)s")
+Succeed = Builder(name = "Succeed", action = "python succeed.py $targets")
+Fail = Builder(name = "Fail", action = "python fail.py $targets")
 env = Environment(BUILDERS = [Succeed, Fail])
 env.Fail(target = 'aaa.1', source = 'aaa.in')
 env.Succeed(target = 'aaa.out', source = 'aaa.1')
 
 test.write('SConstruct', """
 MyBuild = Builder(name = "MyBuild",
-		  action = "python build.py %(target)s")
+		  action = "python build.py $targets")
 env = Environment(BUILDERS = [MyBuild])
 env.MyBuild(target = 'f1.out', source = 'f1.in')
 env.MyBuild(target = 'f2.out', source = 'f2.in')
 
 test.write('SConstruct', """
 MyBuild = Builder(name = "MyBuild",
-		  action = "python build.py %(target)s")
+		  action = "python build.py $target")
 env = Environment(BUILDERS = [MyBuild])
 env.MyBuild(target = 'f1.out', source = 'f1.in')
 env.MyBuild(target = 'f2.out', source = 'f2.in')

test/up-to-date.py

 """)
 
 test.write('SConstruct', """
-B = Builder(name = "B", action = "python build.py %(target)s %(source)s")
+B = Builder(name = "B", action = "python build.py $targets $sources")
 env = Environment(BUILDERS = [B])
 env.B(target = 'f1.out', source = 'f1.in')
 env.B(target = 'f2.out', source = 'f2.in')