Composite Builder and related changes from Charles Crain.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 18 Oct 2001 02:46:26 +0000 (02:46 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 18 Oct 2001 02:46:26 +0000 (02:46 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@103 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/engine/SCons/Builder.py
src/engine/SCons/BuilderTests.py
src/engine/SCons/Defaults.py
test/Object.py

index 1e3c9b9aa494f39e2c15b578a106d1f5865bbf35..7febeb2538f414f6bd1adb17dd360040840350a6 100644 (file)
@@ -37,6 +37,15 @@ import SCons.Node.FS
 from SCons.Util import PathList, scons_str2nodes, scons_subst
 import string
 import types
+from UserList import UserList
+from UserDict import UserDict
+from Errors import UserError
+
+try:
+    from UserString import UserString
+except ImportError:
+    class UserString:
+        pass
 
 
 
@@ -98,8 +107,20 @@ elif os.name == 'nt':
 
 def Builder(**kw):
     """A factory for builder objects."""
-    if kw.has_key('builders'):
+    if kw.has_key('src_builder'):
         return apply(MultiStepBuilder, (), kw)
+    elif kw.has_key('action') and (type(kw['action']) is types.DictType or
+                                   isinstance(kw['action'], UserDict)):
+        action_dict = kw['action']
+        builders = []
+        for suffix, action in action_dict.items():
+            bld_kw = kw.copy()
+            bld_kw['action'] = action
+            bld_kw['src_suffix'] = suffix
+            builders.append(apply(BuilderBase, (), bld_kw))
+        del kw['action']
+        kw['builders'] = builders
+        return apply(CompositeBuilder, (), kw)
     else:
         return apply(BuilderBase, (), kw)
 
@@ -112,19 +133,20 @@ class BuilderBase:
 
     def __init__(self, name = None,
                        action = None,
-                       prefix = None,
-                       suffix = None,
-                       src_suffix = None,
+                       prefix = '',
+                       suffix = '',
+                       src_suffix = '',
                        node_factory = SCons.Node.FS.default_fs.File):
        self.name = name
        self.action = Action(action)
+
        self.prefix = prefix
        self.suffix = suffix
        self.src_suffix = src_suffix
        self.node_factory = node_factory
-       if not self.suffix is None and self.suffix[0] != '.':
+        if self.suffix and self.suffix[0] not in '.$':
            self.suffix = '.' + self.suffix
-       if not self.src_suffix is None and self.src_suffix[0] != '.':
+        if self.src_suffix and self.src_suffix[0] not in '.$':
            self.src_suffix = '.' + self.src_suffix
 
     def __cmp__(self, other):
@@ -145,9 +167,12 @@ class BuilderBase:
                ret.append(f)
            return ret
 
-       tlist = scons_str2nodes(adjustixes(target, self.prefix, self.suffix),
+       tlist = scons_str2nodes(adjustixes(target,
+                                           env.subst(self.prefix),
+                                           env.subst(self.suffix)),
                                 self.node_factory)
-       slist = scons_str2nodes(adjustixes(source, None, self.src_suffix),
+       slist = scons_str2nodes(adjustixes(source, None,
+                                           env.subst(self.src_suffix)),
                                 self.node_factory)
        for t in tlist:
            t.builder_set(self)
@@ -163,62 +188,37 @@ class BuilderBase:
        """
        return apply(self.action.execute, (), kw)
 
-class BuilderProxy:
-    """This base class serves as a proxy to a builder object,
-    exposing the same interface, but just forwarding calls to
-    the underlying object.  Use it for subclass Builders that
-    need to wrap or decorate another Builder class."""
-    def __init__(self, builder):
-        self.subject = builder
-
-    def __call__(self, env, target = None, source = None):
-        return self.subject.__call__(env, target, source)
-
-    def execute(self, **kw):
-        return apply(self.subject.execute, (), kw)
-    
-    def __cmp__(self, other):
-        return cmp(self.__dict__, other.__dict__)
-
-    def __getattr__(self, name):
-        assert 'subject' in self.__dict__.keys(), \
-               "You must call __init__() on the BuilderProxy base."
-        return getattr(self.subject, name)
-
 class MultiStepBuilder(BuilderBase):
     """This is a builder subclass that can build targets in
-    multiple steps according to the suffixes of the source files.
-    Given one or more "subordinate" builders in its constructor,
-    this class will apply those builders to any files matching
-    the builder's src_suffix, using a file of the same name
-    as the source, but with src_suffix changed to suffix.
-    The targets of these builders then become sources for this
-    builder.
+    multiple steps.  The src_builder parameter to the constructor
+    accepts a builder that is called to build sources supplied to
+    this builder.  The targets of that first build then become
+    the sources of this builder.
+
+    If this builder has a src_suffix supplied, then the src_builder
+    builder is NOT invoked if the suffix of a source file matches
+    src_suffix.
     """
-    def __init__(self,  name = None,
+    def __init__(self,  src_builder,
+                        name = None,
                        action = None,
-                       prefix = None,
-                       suffix = None,
-                       src_suffix = None,
-                        node_factory = SCons.Node.FS.default_fs.File,
-                        builders = []):
+                       prefix = '',
+                       suffix = '',
+                       src_suffix = '',
+                       node_factory = SCons.Node.FS.default_fs.File):
         BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
                              node_factory)
-        self.builder_dict = {}
-        for bld in builders:
-            if bld.suffix and bld.src_suffix:
-                self.builder_dict[bld.src_suffix] = bld
+        self.src_builder = src_builder
 
     def __call__(self, env, target = None, source = None):
         slist = scons_str2nodes(source, self.node_factory)
         final_sources = []
+        src_suffix = env.subst(self.src_suffix)
         for snode in slist:
             path, ext = os.path.splitext(snode.path)
-            if self.builder_dict.has_key(ext):
-                bld = self.builder_dict[ext]
-                tgt = bld(env,
-                          target=[ path+bld.suffix, ],
-                          source=snode)
+            if not src_suffix or ext != src_suffix:
+                tgt = self.src_builder(env, target = [ path ],
+                                     source=snode)
                 if not type(tgt) is types.ListType:
                     final_sources.append(tgt)
                 else:
@@ -228,26 +228,68 @@ class MultiStepBuilder(BuilderBase):
         return BuilderBase.__call__(self, env, target=target,
                                     source=final_sources)
 
-print_actions = 1;
-execute_actions = 1;
+class CompositeBuilder(BuilderBase):
+    """This is a convenient Builder subclass that can build different
+    files based on their suffixes.  For each target, this builder
+    will examine the target's sources.  If they are all the same
+    suffix, and that suffix is equal to one of the child builders'
+    src_suffix, then that child builder will be used.  Otherwise,
+    UserError is thrown.
+
+    Child builders are supplied via the builders arg to the
+    constructor.  Each must have its src_suffix set."""
+    def __init__(self,  name = None,
+                        prefix='',
+                        suffix='',
+                        builders=[]):
+        BuilderBase.__init__(self, name=name, prefix=prefix,
+                             suffix=suffix)
+        self.builder_dict = {}
+        for bld in builders:
+            if not bld.src_suffix:
+                raise InternalError, "All builders supplied to CompositeBuilder class must have a src_suffix."
+            self.builder_dict[bld.src_suffix] = bld
 
+    def __call__(self, env, target = None, source = None):
+        ret = BuilderBase.__call__(self, env, target=target, source=source)
 
+        builder_dict = {}
+        for suffix, bld in self.builder_dict.items():
+            builder_dict[env.subst(bld.src_suffix)] = bld
+
+        if type(ret) is types.ListType:
+            tlist = ret
+        else:
+            tlist = [ ret ]
+        for tnode in tlist:
+            suflist = map(lambda x: os.path.splitext(x.path)[1],
+                          tnode.sources)
+            last_suffix=''
+            for suffix in suflist:
+                if last_suffix and last_suffix != suffix:
+                    raise UserError, "The builder for %s is only capable of building source files of identical suffixes." % tnode.path
+                last_suffix = suffix
+            if last_suffix:
+                try:
+                    tnode.builder_set(builder_dict[last_suffix])
+                except KeyError:
+                    raise UserError, "Builder not capable of building files with suffix: %s" % suffix
+        return ret
+
+print_actions = 1;
+execute_actions = 1;
 
 def Action(act):
     """A factory for action objects."""
-    if type(act) == types.StringType:
-       l = string.split(act, "\n")
-       if len(l) > 1:
-           act = l
     if callable(act):
        return FunctionAction(act)
-    elif type(act) == types.StringType:
+    elif type(act) == types.StringType or isinstance(act, UserString):
        return CommandAction(act)
-    elif type(act) == types.ListType:
+    elif type(act) == types.ListType or isinstance(act, UserList):
        return ListAction(act)
     else:
        return None
-
+    
 class ActionBase:
     """Base class for actions that create output objects.
     
@@ -282,19 +324,22 @@ class CommandAction(ActionBase):
        if kw.has_key('env'):
            glob = kw['env']
 
-       cmd = scons_subst(self.command, loc, glob)
-       if print_actions:
-           self.show(cmd)
-       ret = 0
-       if execute_actions:
-            args = string.split(cmd)
-            try:
-                ENV = glob['ENV']
-            except:
-                import SCons.Defaults
-                ENV = SCons.Defaults.ConstructionEnvironment['ENV']
-           ret = spawn(args[0], args, ENV)
-       return ret
+       cmd_str = scons_subst(self.command, loc, glob)
+        for cmd in string.split(cmd_str, '\n'):
+            if print_actions:
+                self.show(cmd)
+            if execute_actions:
+                args = string.split(cmd)
+                try:
+                    ENV = glob['ENV']
+                except:
+                    import SCons.Defaults
+                    ENV = SCons.Defaults.ConstructionEnvironment['ENV']
+                ret = spawn(args[0], args, ENV)
+                if ret:
+                    #XXX This doesn't account for ignoring errors (-i)
+                    return ret
+        return 0
 
 
 
index a446f951c2bd090a3886b9e7e4eb4eeeb20e9163..6c9c67896215dd2d68ee07c015f546d343807ce2 100644 (file)
@@ -240,7 +240,7 @@ class BuilderTestCase(unittest.TestCase):
        assert builder.prefix == 'lib'
        tgt = builder(env, target = 'tgt1', source = 'src1')
        assert tgt.path == 'libtgt1', \
-               "Target has unexpected name: %s" % tgt[0].path
+               "Target has unexpected name: %s" % tgt.path
 
     def test_src_suffix(self):
        """Test Builder creation with a specified source file suffix
@@ -276,7 +276,7 @@ class BuilderTestCase(unittest.TestCase):
                                         src_suffix='.bar',
                                         suffix='.foo')
         builder2 = SCons.Builder.MultiStepBuilder(action='foo',
-                                                  builders = [ builder1 ])
+                                                  src_builder = builder1)
         tgt = builder2(env, target='baz', source='test.bar test2.foo test3.txt')
         flag = 0
         for snode in tgt.sources:
@@ -285,6 +285,24 @@ class BuilderTestCase(unittest.TestCase):
                 assert snode.sources[0].path == 'test.bar'
         assert flag
 
+    def test_CompositeBuilder(self):
+        """Testing CompositeBuilder class."""
+        builder = SCons.Builder.Builder(action={ '.foo' : 'foo',
+                                                 '.bar' : 'bar' })
+        
+        assert isinstance(builder, SCons.Builder.CompositeBuilder)
+        tgt = builder(env, target='test1', source='test1.foo')
+        assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
+        assert tgt.builder.action.command == 'foo'
+        tgt = builder(env, target='test2', source='test2.bar')
+        assert tgt.builder.action.command == 'bar'
+        flag = 0
+        try:
+            tgt = builder(env, target='test2', source='test2.bar test1.foo')
+        except SCons.Errors.UserError:
+            flag = 1
+        assert flag, "UserError should be thrown when we build targets with files of different suffixes."
+
 
 if __name__ == "__main__":
     suite = unittest.makeSuite(BuilderTestCase, 'test_')
index 086c1d55188ddae9d61fc0c17e7ca7571e8811e3..1c5fda53b8e3076460b2ef45bc29c2305c8c03ff 100644 (file)
@@ -38,65 +38,83 @@ import SCons.Builder
 
 
 
-if os.name == 'posix':
-
-    object_suffix = '.o'
-    program_suffix = None
-    library_prefix = 'lib'
-    library_suffix = '.a'
-
-elif os.name == 'nt':
-
-    object_suffix = '.obj'
-    program_suffix = '.exe'
-    library_prefix = None
-    library_suffix = '.lib'
-
-
-
 Object = SCons.Builder.Builder(name = 'Object',
-                               action = '$CCCOM',
-                               suffix = object_suffix,
-                               src_suffix = '.c')
+                               action = { '.c'   : '$CCCOM',
+                                          '.C'   : '$CXXCOM',
+                                          '.cc'  : '$CXXCOM',
+                                          '.cpp' : '$CXXCOM',
+                                          '.cxx' : '$CXXCOM',
+                                          '.c++' : '$CXXCOM',
+                                          '.C++' : '$CXXCOM',
+                                        },
+                               prefix = '$OBJPREFIX',
+                               suffix = '$OBJSUFFIX')
 
 Program = SCons.Builder.Builder(name = 'Program',
                                 action = '$LINKCOM',
-                                suffix = program_suffix,
-                                builders = [ Object ])
+                                prefix = '$PROGPREFIX',
+                                suffix = '$PROGSUFFIX',
+                                src_suffix = '$OBJSUFFIX',
+                                src_builder = Object)
 
 Library = SCons.Builder.Builder(name = 'Library',
-                                action = 'ar r $target $sources\nranlib $target',
-                                prefix = library_prefix,
-                                suffix = library_suffix,
-                                builders = [ Object ])
+                                action = '$ARCOM',
+                                prefix = '$LIBPREFIX',
+                                suffix = '$LIBSUFFIX',
+                                src_suffix = '$OBJSUFFIX',
+                                src_builder = Object)
 
 
 
 if os.name == 'posix':
 
     ConstructionEnvironment = {
-        'CC'        : 'cc',
-        'CCFLAGS'   : '',
-        'CCCOM'     : '$CC $CCFLAGS -c -o $target $sources',
-        'LINK'      : '$CC',
-        'LINKFLAGS' : '',
-        'LINKCOM'   : '$LINK $LINKFLAGS -o $target $sources',
-        'BUILDERS'  : [Object, Program, Library],
-        'ENV'       : { 'PATH' : '/usr/local/bin:/bin:/usr/bin' },
+        'CC'         : 'cc',
+        'CCFLAGS'    : '',
+        'CCCOM'      : '$CC $CCFLAGS -c -o $target $sources',
+        'CXX'        : 'c++',
+        'CXXFLAGS'   : '$CCFLAGS',
+        'CXXCOM'     : '$CXX $CXXFLAGS -c -o $target $sources',
+        'LINK'       : '$CXX',
+        'LINKFLAGS'  : '',
+        'LINKCOM'    : '$LINK $LINKFLAGS -o $target $sources',
+        'AR'         : 'ar',
+        'ARFLAGS'    : 'r',
+        'ARCOM'      : '$AR $ARFLAGS $target $sources\nranlib $target',
+        'BUILDERS'   : [Object, Program, Library],
+        'OBJPREFIX'  : '',
+        'OBJSUFFIX'  : '.o',
+        'PROGPREFIX' : '',
+        'PROGSUFFIX' : '',
+        'LIBPREFIX'  : 'lib',
+        'LIBSUFFIX'  : '.a',
+        'ENV'        : { 'PATH' : '/usr/local/bin:/bin:/usr/bin' },
     }
 
 elif os.name == 'nt':
 
     ConstructionEnvironment = {
-        'CC'        : 'cl',
-        'CCFLAGS'   : '/nologo',
-        'CCCOM'     : '$CC $CCFLAGS /c $sources /Fo$target',
-        'LINK'      : 'link',
-        'LINKFLAGS' : '',
-        'LINKCOM'   : '$LINK $LINKFLAGS /out:$target $sources',
-        'BUILDERS'  : [Object, Program, Library],
-        'ENV'       : {
+        'CC'         : 'cl',
+        'CCFLAGS'    : '/nologo',
+        'CCCOM'      : '$CC $CCFLAGS /c $sources /Fo$target',
+        'CXX'        : '$CC',
+        'CXXFLAGS'   : '$CCFLAGS',
+        'CXXCOM'     : '$CXX $CXXFLAGS /c $sources /Fo$target',
+        'LINK'       : 'link',
+        'LINKFLAGS'  : '',
+        'LINKCOM'    : '$LINK $LINKFLAGS /out:$target $sources',
+        'AR'         : 'lib',
+        'ARFLAGS'    : '/nologo',
+        'ARCOM'      : '$AR $ARFLAGS /out:$target $sources',
+        'BUILDERS'   : [Object, Program, Library],
+        'OBJPREFIX'  : '',
+        'OBJSUFFIX'  : '.obj',
+        'PROGPREFIX' : '',
+        'PROGSUFFIX' : '.exe',
+        'LIBPREFIX'  : '',
+        'LIBSUFFIX'  : '.lib',
+        'ENV'        : {
                         'PATH'    : r'C:\Python20;C:\WINNT\system32;C:\WINNT;C:\Program Files\Microsoft Visual Studio\VC98\Bin\;',
-                        'PATHEXT' : '.COM;.EXE;.BAT;.CMD'
+                        'PATHEXT' : '.COM;.EXE;.BAT;.CMD',
                       },
     }
index 9d6b07d8d3014365e7399903973bd591d8c74fe0..53bab3692c7c8cf2be7db320ea251cd13d767c50 100644 (file)
@@ -28,15 +28,14 @@ import TestSCons
 
 test = TestSCons.TestSCons()
 
-test.pass_test()       #XXX Short-circuit until this is supported.
-
 test.write('SConstruct', """
+env = Environment()
 f1 = env.Object(target = 'f1', source = 'f1.c')
-f2 = env.Object(target = 'f2', source = 'f2.c')
+f2 = env.Object(target = 'f2', source = 'f2.cpp')
 f3 = env.Object(target = 'f3', source = 'f3.c')
-env.Program(target = 'prog1', source = 'f1.o f2.o f3.o prog.c')
-env.Program(target = 'prog2', source = [f1, f2, f3, 'prog.c'])
-env.Program(target = 'prog3', source = ['f1.o', f2, 'f3.o prog.c'])
+env.Program(target = 'prog1', source = 'f1.o f2.o f3.o prog.cpp')
+env.Program(target = 'prog2', source = [f1, f2, f3, 'prog.cpp'])
+env.Program(target = 'prog3', source = ['f1.o', f2, 'f3.o', 'prog.cpp'])
 """)
 
 test.write('f1.c', """
@@ -47,7 +46,9 @@ f1(void)
 }
 """)
 
-test.write('f2.c', """
+test.write('f2.cpp', """
+#include <stdio.h>
+
 void
 f2(void)
 {
@@ -63,10 +64,12 @@ f3(void)
 }
 """)
 
-test.write('prog.c', """
-extern void f1(void);
+test.write('prog.cpp', """
+#include <stdio.h>
+
+extern "C" void f1(void);
 extern void f2(void);
-extern void f3(void);
+extern "C" void f3(void);
 int
 main(int argc, char *argv[])
 {
@@ -75,6 +78,7 @@ main(int argc, char *argv[])
        f2();
        f3();
        printf("prog.c\n");
+       return 0;
 }
 """)