diff options
author | Steven Knight <knight@baldmt.com> | 2001-10-11 23:13:20 +0000 |
---|---|---|
committer | Steven Knight <knight@baldmt.com> | 2001-10-11 23:13:20 +0000 |
commit | 6f4f6e4639a3a4a796b28322ba07b9b81f2c354f (patch) | |
tree | bf1be1f8cebc4b016b9856f27d1cf84879b547be /src | |
parent | 580b07ed247ab0de0ea96093fc2c4baa28c35c65 (diff) | |
download | scons-6f4f6e4639a3a4a796b28322ba07b9b81f2c354f.tar.gz |
Implement special variable substitution.
Diffstat (limited to 'src')
-rw-r--r-- | src/engine/SCons/Builder.py | 27 | ||||
-rw-r--r-- | src/engine/SCons/BuilderTests.py | 48 | ||||
-rw-r--r-- | src/engine/SCons/Defaults.py | 8 | ||||
-rw-r--r-- | src/engine/SCons/Util.py | 120 | ||||
-rw-r--r-- | src/engine/SCons/UtilTests.py | 46 |
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(): |