summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2001-10-11 23:13:20 +0000
committerSteven Knight <knight@baldmt.com>2001-10-11 23:13:20 +0000
commit6f4f6e4639a3a4a796b28322ba07b9b81f2c354f (patch)
treebf1be1f8cebc4b016b9856f27d1cf84879b547be /src
parent580b07ed247ab0de0ea96093fc2c4baa28c35c65 (diff)
downloadscons-6f4f6e4639a3a4a796b28322ba07b9b81f2c354f.tar.gz
Implement special variable substitution.
Diffstat (limited to 'src')
-rw-r--r--src/engine/SCons/Builder.py27
-rw-r--r--src/engine/SCons/BuilderTests.py48
-rw-r--r--src/engine/SCons/Defaults.py8
-rw-r--r--src/engine/SCons/Util.py120
-rw-r--r--src/engine/SCons/UtilTests.py46
5 files changed, 232 insertions, 17 deletions
diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py
index b009fe08..65d4d413 100644
--- a/src/engine/SCons/Builder.py
+++ b/src/engine/SCons/Builder.py
@@ -33,7 +33,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os
import SCons.Node.FS
-import SCons.Util
+from SCons.Util import PathList, scons_str2nodes, scons_varrepl
import string
import types
@@ -63,8 +63,8 @@ class Builder:
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)
@@ -123,8 +123,7 @@ class TargetNamingBuilder(BuilderProxy):
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)
@@ -159,7 +158,7 @@ class MultiStepBuilder(Builder):
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)
@@ -214,7 +213,21 @@ class CommandAction(ActionBase):
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
diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py
index 8554d670..7db69ef2 100644
--- a/src/engine/SCons/BuilderTests.py
+++ b/src/engine/SCons/BuilderTests.py
@@ -59,6 +59,7 @@ class BuilderTestCase(unittest.TestCase):
def test__call__(self):
"""Test calling a builder to establish source dependencies
"""
+ env = Environment()
class Node:
def __init__(self, name):
self.name = name
@@ -110,7 +111,40 @@ class BuilderTestCase(unittest.TestCase):
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")
@@ -119,7 +153,8 @@ class BuilderTestCase(unittest.TestCase):
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):
@@ -128,7 +163,8 @@ class BuilderTestCase(unittest.TestCase):
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):
@@ -138,7 +174,8 @@ class BuilderTestCase(unittest.TestCase):
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)
@@ -158,7 +195,8 @@ class BuilderTestCase(unittest.TestCase):
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
diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py
index 3e1c8a8d..d9120de6 100644
--- a/src/engine/SCons/Defaults.py
+++ b/src/engine/SCons/Defaults.py
@@ -38,16 +38,16 @@ import SCons.Builder
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')
diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py
index 61b7417e..2a6858c2 100644
--- a/src/engine/SCons/Util.py
+++ b/src/engine/SCons/Util.py
@@ -30,8 +30,11 @@ Various utility functions go here.
__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):
@@ -67,3 +70,120 @@ 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
+
+
diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py
index 135a82c9..db4d7a14 100644
--- a/src/engine/SCons/UtilTests.py
+++ b/src/engine/SCons/UtilTests.py
@@ -24,10 +24,12 @@
__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):
@@ -69,6 +71,48 @@ class UtilTestCase(unittest.TestCase):
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():