Deduce the target if it's not supplied.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 3 Jul 2002 15:23:57 +0000 (15:23 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Wed, 3 Jul 2002 15:23:57 +0000 (15:23 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@400 fdb21ef1-2011-0410-befe-b5e4ea1792b1

doc/man/scons.1
src/CHANGES.txt
src/engine/SCons/Builder.py
src/engine/SCons/BuilderTests.py
src/engine/SCons/Environment.py
src/engine/SCons/EnvironmentTests.py

index 0241c9b2a551cf16557d6046cf434ab32bc19fff..f606adb74f2ec8657e44b364ad7fd4e2dabe645e 100644 (file)
@@ -33,7 +33,7 @@
 .RE
 .fi
 ..
-.TH SCONS 1 "May 2002"
+.TH SCONS 1 "July 2002"
 .SH NAME
 scons \- a software construction tool
 .SH SYNOPSIS
@@ -542,16 +542,6 @@ Ignored for compatibility with GNU
 appear up-to-date is unnecessary when using 
 .BR scons .)
 
-.TP
--T
-Works exactly the same way as the
-.B -u
-option except for the way default targets are handled.
-When this option is used and no targets are specified on the command line,
-all default targets that are defined in the SConscript files in the current
-directory are built, regardless of what directory the resulant targets end
-up in.
-
 .TP
 -u, --up, --search-up
 Walks up the directory structure until an 
@@ -757,34 +747,64 @@ yacc
 
 Build rules are specified by calling a construction
 environment's builder methods.
-The arguments to the builder methods are target (a list of
-target files) and source (a list of source files).
-If a string is given
-for target or source, then 
-.B scons 
-.I currently
-interprets it as a space-delimited list of files.
-NOTE:  Splitting a string into a list of files this
-way will be
-.I removed
-as of the next version of SCons.
-If you currently use space-delimited file lists,
-you must change them by next release.
-See the discussion of the Split() function
-for more information.
-
-The following are examples of calling the Program builder:
-
-.ES
-# The recommended ways to call a builder
-# with multiple source input files:
+The arguments to the builder methods are
+.B target
+(a list of target files)
+and
+.B source
+(a list of source files).
+
+Because long lists of file names
+can lead to a lot of quoting,
+.B scons
+supplies a
+.B Split()
+function that splits a single string
+into a list, separated on
+strings of white-space characters.
+(This is similar to the
+string.split() method
+from the standard Python library.)
+
+Like all Python arguments,
+the target and source arguments to a builder
+can be specified either with or without
+the "target" and "source" keywords.
+When the keywords are omitted,
+the target is first,
+followed by the source.
+The following are equivalent examples of calling the Program builder:
+
+.ES
 env.Program('bar', ['bar.c', 'foo.c'])
 env.Program('bar', Split('bar.c foo.c'))
+env.Program(source =  ['bar.c', 'foo.c'], target = 'bar')
+env.Program(target = 'bar', Split('bar.c foo.c'))
+env.Program('bar', source = string.split('bar.c foo.c'))
+.EE
 
-# Space-delimited lists.
-# The following will NOT work in version 0.08 of SCons!
-env.Program(target = 'bar', source = 'bar.c foo.c')
-env.Program('bar', 'bar.c foo.c')
+When the target shares the same base name
+as the source and only the suffix varies,
+and if the builder has a suffix defined for the target file type,
+then the target argument may be omitted completely,
+and
+.B scons
+will deduce the target file name from
+the source file name.
+The following examples all build the
+executable program
+.B bar
+(on POSIX systems)
+or 
+.B bar.exe
+(on Windows sytems)
+from the bar.c source file:
+
+.ES
+env.Program(target = 'bar', source = 'bar.c')
+env.Program('bar', source = 'bar.c')
+env.Program(source = 'bar.c')
+env.Program('bar.c')
 .EE
 
 .B scons
@@ -820,8 +840,13 @@ Source files must have one of the following extensions:
   .SPP    assembly language file + C pre-processor
 .EE
 .IP
-The target object file prefix and suffix (if any) are automatically
-added. Examples:
+The target object file prefix
+(specified by the $OBJPREFIX construction variable; nothing by default)
+and suffix
+(specified by the $OBJSUFFIX construction variable;
+\.obj on Windows systems, .o on POSIX systems)
+are automatically added to the target if not already present.
+Examples:
 
 .ES
 env.StaticObject(target = 'aaa', source = 'aaa.c')
@@ -836,8 +861,13 @@ Source files must have one of the same set of extensions
 specified above for the
 .B StaticObject
 builder.
-The target object file prefix and suffix (if any) are automatically
-added. Examples:
+The target object file prefix
+(specified by the $OBJPREFIX construction variable; nothing by default)
+and suffix
+(specified by the $OBJSUFFIX construction variable;
+\.obj on Windows systems, .o on POSIX systems)
+are automatically added to the target if not already present.
+Examples:
 
 .ES
 env.SharedObject(target = 'ddd', source = 'ddd.c')
@@ -861,11 +891,16 @@ builder;
 see that builder's description for
 a list of legal source file suffixes
 and how they are interpreted.
-The executable prefix and suffix (if any) are
-automatically added to the target. Example:
+The target executable file prefix
+(specified by the $PROGPREFIX construction variable; nothing by default)
+and suffix
+(specified by the $PROGSUFFIX construction variable;
+by default, .exe on Windows systems, nothing on POSIX systems)
+are automatically added to the target if not already present.
+Example:
 
 .ES
-env.Program(target = 'foo', source = 'foo.o bar.c baz.f')
+env.Program(target = 'foo', source = ['foo.o', 'bar.c', 'baz.f'])
 .EE
 
 .IP StaticLibrary
@@ -874,14 +909,22 @@ or C, C++ or Fortran source files.
 If any source files are given,
 then they will be automatically
 compiled to object files.
-The library prefix and suffix (if any)
+The static library prefix and suffix (if any)
 are automatically added to the target.
+The target library file prefix
+(specified by the $LIBPREFIX construction variable;
+by default, lib on POSIX systems, nothing on Windows systems)
+and suffix
+(specified by the $LIBSUFFIX construction variable;
+by default, .lib on Windows systems, .a on POSIX systems)
+are automatically added to the target if not already present.
 Example:
 
 .ES
-env.StaticLibrary(target = 'bar', source = 'bar.c foo.o')
+env.StaticLibrary(target = 'bar', source = ['bar.c', 'foo.o'])
 .EE
 
+.IP
 Any object files listed in the
 .B source
 must have been built for a static library
@@ -899,12 +942,19 @@ or C, C++ or Fortran source files.
 If any source files are given,
 then they will be automatically
 compiled to object files.
-The library prefix and suffix (if any)
+The static library prefix and suffix (if any)
 are automatically added to the target.
+The target library file prefix
+(specified by the $SHLIBPREFIX construction variable;
+by default, lib on POSIX systems, nothing on Windows systems)
+and suffix
+(specified by the $SHLIBSUFFIX construction variable;
+by default, .dll on Windows systems, .so on POSIX systems)
+are automatically added to the target if not already present.
 Example:
 
 .ES
-env.SharedLibrary(target = 'bar', source = 'bar.c foo.o')
+env.SharedLibrary(target = 'bar', source = ['bar.c', 'foo.o'])
 .EE
 .IP
 On WIN32 systems, the
@@ -984,7 +1034,7 @@ if it is not already present.  Example:
 .ES
 # builds from aaa.tex
 env.PDF(target = 'aaa.pdf', source = 'aaa.tex')
-# builds bbb.dvi
+# builds bbb.pdf from bbb.dvi
 env.PDF(target = 'bbb', source = 'bbb.dvi')
 .EE
 
@@ -999,7 +1049,7 @@ if it is not already present.  Example:
 .ES
 # builds from aaa.tex
 env.PostScript(target = 'aaa.ps', source = 'aaa.tex')
-# builds bbb.dvi
+# builds bbb.ps from bbb.dvi
 env.PostScript(target = 'bbb', source = 'bbb.dvi')
 .EE
 .LP
@@ -1309,6 +1359,15 @@ and the $ASPPCOM command line
 used to assemble an assembly language source file,
 after first running each file through the C preprocessor.
 
+.IP _CPPINCFLAGS
+An automatically-generated construction variable
+containing the C preprocessor command-line options
+for specifying directories to be searched for include files.
+The value of $_CPPINCFLAGS is created
+by appending $INCPREFIX and $INCSUFFIX
+to the beginning and end
+of each directory in $CPPPATH.
+
 .IP CPPPATH
 The list of directories that the C preprocessor will search for include
 directories. The C/C++ implicit dependency scanner will search these
@@ -1334,6 +1393,25 @@ include = Dir('include')
 env = Environment(CPPPATH=include)
 .EE
 
+.IP
+The directory list will be added to command lines
+through the automatically-generated
+$_CPPINCFLAGS
+construction variable,
+which is constructed by
+appending the values of the
+$INCPREFIX and $INCSUFFIX
+construction variables
+to the beginning and end
+of each directory in $CPPPATH.
+Any command lines you define that need
+the CPPPATH directory list should
+include $_CPPINCFLAGS:
+
+.ES
+env = Environment(CCCOM="my_compiler $_CPPINCFLAGS -c -o $TARGET $SOURCE")
+.EE
+
 .IP CXX
 The C++ compiler.
 
@@ -1429,6 +1507,15 @@ The command line used to compile a Fortran source file to an object file.
 .IP F77FLAGS
 General options that are passed to the Fortran compiler.
 
+.IP _F77INCFLAGS
+An automatically-generated construction variable
+containing the Fortran compiler command-line options
+for specifying directories to be searched for include files.
+The value of $_F77INCFLAGS is created
+by appending $INCPREFIX and $INCSUFFIX
+to the beginning and end
+of each directory in $F77PATH.
+
 .IP F77PATH
 The list of directories that the Fortran compiler will search for include
 directories. The Fortran implicit dependency scanner will search these
@@ -1454,6 +1541,25 @@ include = Dir('include')
 env = Environment(F77PATH=include)
 .EE
 
+.IP
+The directory list will be added to command lines
+through the automatically-generated
+$_F77INCFLAGS
+construction variable,
+which is constructed by
+appending the values of the
+$INCPREFIX and $INCSUFFIX
+construction variables
+to the beginning and end
+of each directory in $F77PATH.
+Any command lines you define that need
+the F77PATH directory list should
+include $_F77INCFLAGS:
+
+.ES
+env = Environment(F77COM="my_compiler $_F77INCFLAGS -c -o $TARGET $SOURCE")
+.EE
+
 .IP F77PPCOM 
 The command line used to compile a Fortran source file to an object file
 after first running the file through the C preprocessor.
@@ -1463,10 +1569,18 @@ are included on this command line.
 .IP INCPREFIX
 The prefix used to specify an include directory on the C compiler command
 line.
+This will be appended to the beginning of each directory
+in the $CPPPATH and $F77PATH construction variables
+when the $_CPPINCFLAGS and $_F77INCFLAGS
+variables are automatically generated.
 
 .IP INCSUFFIX
 The suffix used to specify an include directory on the C compiler command
 line.
+This will be appended to the end of each directory
+in the $CPPPATH and $F77PATH construction variables
+when the $_CPPINCFLAGS and $_F77INCFLAGS
+variables are automatically generated.
 
 .IP LATEX
 The LaTeX structured formatter and typesetter.
@@ -1487,17 +1601,47 @@ General options passed to the lexical analyzer generator.
 The command line used to call the lexical analyzer generator
 to generate a source file.
 
+.IP _LIBDIRFLAGS
+An automatically-generated construction variable
+containing the linker command-line options
+for specifying directories to be searched for library.
+The value of $_LIBDIRFLAGS is created
+by appending $LIBDIRPREFIX and $LIBDIRSUFFIX
+to the beginning and end
+of each directory in $LIBPATH.
+
 .IP LIBDIRPREFIX
 The prefix used to specify a library directory on the linker command line.
+This will be appended to the beginning of each directory
+in the $LIBPATH construction variable
+when the $_LIBDIRFLAGS variable is automatically generated.
 
 .IP LIBDIRSUFFIX
 The suffix used to specify a library directory on the linker command line.
+This will be appended to the end of each directory
+in the $LIBPATH construction variable
+when the $_LIBDIRFLAGS variable is automatically generated.
+
+.IP _LIBFLAGS
+An automatically-generated construction variable
+containing the linker command-line options
+for specifying libraries to be linked with the resulting target.
+The value of $_LIBFLAGS is created
+by appending $LIBLINKPREFIX and $LIBLINKSUFFIX
+to the beginning and end
+of each directory in $LIBS.
 
 .IP LIBLINKPREFIX
 The prefix used to specify a library to link on the linker command line.
+This will be appended to the beginning of each library
+in the $LIBS construction variable
+when the $_LIBFLAGS variable is automatically generated.
 
 .IP LIBLINKSUFFIX
 The suffix used to specify a library to link on the linker command line.
+This will be appended to the end of each library
+in the $LIBS construction variable
+when the $_LIBFLAGS variable is automatically generated.
 
 .IP LIBPATH
 The list of directories that will be searched for libraries.
@@ -1524,6 +1668,25 @@ libs = Dir('libs')
 env = Environment(LIBPATH=libs)
 .EE
 
+.IP
+The directory list will be added to command lines
+through the automatically-generated
+$_LIBDIRFLAGS
+construction variable,
+which is constructed by
+appending the values of the
+$LIBDIRPREFIX and $LIBDIRSUFFIX
+construction variables
+to the beginning and end
+of each directory in $LIBPATH.
+Any command lines you define that need
+the LIBPATH directory list should
+include $_LIBDIRFLAGS:
+
+.ES
+env = Environment(LINKCOM="my_linker $_LIBDIRFLAGS $_LIBFLAGS -o $TARGET $SOURCE")
+.EE
+
 .IP LIBPREFIX
 The prefix used for (static) library file names.
 
@@ -1536,6 +1699,25 @@ that will be linked with
 any executable programs
 created by this environment.
 
+.IP
+The library list will be added to command lines
+through the automatically-generated
+$_LIBFLAGS
+construction variable,
+which is constructed by
+appending the values of the
+$LIBLINKPREFIX and $LIBLINKSUFFIX
+construction variables
+to the beginning and end
+of each directory in $LIBS.
+Any command lines you define that need
+the LIBS library list should
+include $_LIBFLAGS:
+
+.ES
+env = Environment(LINKCOM="my_linker $_LIBDIRFLAGS $_LIBFLAGS -o $TARGET $SOURCE")
+.EE
+
 .IP LIBSUFFIX 
 The suffix used for (static) library file names.
 
@@ -1953,17 +2135,6 @@ files = Split("""
        f6.c
 """)
 .EE
-.IP
-NOTE:  Currently, all builders perform this white-space split
-automatically on their target and source file arguments.
-As of the next version of SCons,
-Builder objects will no longer perform this split.
-If you use white-space separated strings of file names,
-you will need to convert them to lists
-by the next release of SCons by hand,
-or by using the Split() function provided here,
-or by using a similar function such as the
-string.split() function in the Python library.
 
 .TP
 .RI Tool( string )
@@ -2608,12 +2779,7 @@ env.Program(target = 'prog', source = 'p1.c p2.c')
 
 .SS Defining Your Own Builder Object
 
-You
-.I must
-specify a "name" keyword argument for the builder,
-as that becomes the Environment method name
-you use to call the builder.
-Notice also that you can leave off the target file suffix,
+Notice that you can leave off the target file suffix,
 and the builder will add it automatically.
 
 .ES
index 8146a2619bd89ce22d2896e554fc2a1ad3acc8a6..0054f35a00a246cf23299ca1808d32387de66d0f 100644 (file)
@@ -71,6 +71,10 @@ RELEASE 0.08 -
 
   - Add support for the GNU as, Microsoft masm, and nasm assemblers.
 
+  - Allow the "target" argument to a Builder call to be omitted, in
+    which case the target(s) are deduced from the source file(s) and the
+    Builder's specified suffix.
+
   From Jeff Petkau:
 
   - Fix --implicit-cache if the scanner returns an empty list.
index 0b54303b459cf7c5458432e834d33c0b9ff45d97..c4e864308cf991cb38f8a0756f72e7e958587c20 100644 (file)
@@ -56,6 +56,11 @@ import SCons.Node.FS
 import SCons.Util
 import SCons.Warnings
 
+class _Null:
+    pass
+
+_null = _Null
+
 class DictCmdGenerator:
     """This is a callable class that can be used as a
     command generator function.  It holds on to a dictionary
@@ -142,6 +147,8 @@ def _init_nodes(builder, env, args, tlist, slist):
             s.source_scanner = scanner
 
     for t in tlist:
+        if t.side_effect:
+            raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
         if t.builder is not None:
             if t.env != env: 
                 raise UserError, "Two different environments were specified for the same target: %s"%str(t)
@@ -267,11 +274,11 @@ class BuilderBase:
                         path, fn = os.path.split(os.path.normpath(f))
                         f = os.path.join(path, pre + fn)
                     # Only append a suffix if the file does not have one.
-                   if suf and not os.path.splitext(f)[1]:
-                       if f[-len(suf):] != suf:
-                           f = f + suf
-               ret.append(f)
-           return ret
+                    if suf and not os.path.splitext(f)[1]:
+                        if f[-len(suf):] != suf:
+                            f = f + suf
+                ret.append(f)
+            return ret
 
         pre = self.get_prefix(env)
         suf = self.get_suffix(env)
@@ -290,14 +297,20 @@ class BuilderBase:
             emit_args.update(args)
             target, source = apply(self.emitter, (), emit_args)
 
-        tlist = SCons.Node.arg2nodes(adjustixes(target, pre, suf),
-                                     self.target_factory)
         slist = SCons.Node.arg2nodes(adjustixes(source, None, src_suf),
                                      self.source_factory)
+        if target is None:
+            target = map(lambda x, s=suf: os.path.splitext(str(x))[0] + s,
+                         slist)
+        tlist = SCons.Node.arg2nodes(adjustixes(target, pre, suf),
+                                     self.target_factory)
 
         return tlist, slist
 
-    def __call__(self, env, target = None, source = None, **kw):
+    def __call__(self, env, target = None, source = _null, **kw):
+        if source is _null:
+            source = target
+            target = None
         tlist, slist = self._create_nodes(env, kw, target, source)
 
         if len(tlist) == 1:
@@ -456,7 +469,7 @@ class MultiStepBuilder(BuilderBase):
                 src_bld = sdict[ext]
 
                 dictArgs = copy.copy(kw)
-                dictArgs['target'] = [ path + src_bld.get_suffix(env) ]
+                dictArgs['target'] = [path]
                 dictArgs['source'] = snode
                 dictArgs['env'] = env
                 tgt = apply(src_bld, (), dictArgs)
index c0ed108e83eb56a387562e5d51b7e6703a5cc099..8451d995f644903f99bb974563ab447560bf5b43 100644 (file)
@@ -107,6 +107,7 @@ class BuilderTestCase(unittest.TestCase):
                 self.name = name
                 self.sources = []
                 self.builder = None
+                self.side_effect = 0
             def __str__(self):
                 return self.name
             def builder_set(self, builder):
@@ -474,7 +475,7 @@ class BuilderTestCase(unittest.TestCase):
                 "Source has unexpected name: %s" % tgt.sources[0].path
 
         tgt = b1(env, target = 'tgt3', source = 'src3a src3b')
-       assert len(tgt.sources) == 1
+        assert len(tgt.sources) == 1
         assert tgt.sources[0].path == 'src3a src3b.c', \
                 "Unexpected tgt.sources[0] name: %s" % tgt.sources[0].path
 
@@ -578,9 +579,9 @@ class BuilderTestCase(unittest.TestCase):
                                                   action='bar',
                                                   src_builder = builder1,
                                                   src_suffix = '.foo')
-        tgt = builder2(env, target='baz', source=['test.bleh.bar', 'test2.foo', 'test3.txt'])
-        assert str(tgt.sources[0]) == 'test.bleh.foo', str(tgt.sources[0])
-        assert str(tgt.sources[0].sources[0]) == 'test.bleh.bar', \
+        tgt = builder2(env, target='baz', source=['test.bar', 'test2.foo', 'test3.txt'])
+        assert str(tgt.sources[0]) == 'test.foo', str(tgt.sources[0])
+        assert str(tgt.sources[0].sources[0]) == 'test.bar', \
                str(tgt.sources[0].sources[0])
         assert str(tgt.sources[1]) == 'test2.foo', str(tgt.sources[1])
         assert str(tgt.sources[2]) == 'test3.txt', str(tgt.sources[2])
@@ -654,7 +655,7 @@ class BuilderTestCase(unittest.TestCase):
         assert isinstance(tgt.builder, SCons.Builder.MultiStepBuilder)
 
         flag = 0
-        tgt = builder(env, target='t5', source=[ 'test5a.foo',  'test5b.inb' ])
+        tgt = builder(env, target='t5', source='test5a.foo test5b.inb')
         try:
             tgt.build()
         except SCons.Errors.UserError:
@@ -662,7 +663,7 @@ class BuilderTestCase(unittest.TestCase):
         assert flag, "UserError should be thrown when we build targets with files of different suffixes."
 
         flag = 0
-        tgt = builder(env, target='t6', source=[ 'test6a.bar',  'test6b.ina' ])
+        tgt = builder(env, target='t6', source='test6a.bar test6b.ina')
         try:
             tgt.build()
         except SCons.Errors.UserError:
@@ -670,7 +671,7 @@ class BuilderTestCase(unittest.TestCase):
         assert flag, "UserError should be thrown when we build targets with files of different suffixes."
 
         flag = 0
-        tgt = builder(env, target='t4', source=[ 'test4a.ina',  'test4b.inb' ])
+        tgt = builder(env, target='t4', source='test4a.ina test4b.inb')
         try:
             tgt.build()
         except SCons.Errors.UserError:
@@ -775,6 +776,64 @@ class BuilderTestCase(unittest.TestCase):
                                         emitter="$FOO")
         assert builder2 == builder2a, repr(builder2.__dict__) + "\n" + repr(builder2a.__dict__)
 
+    def test_no_target(self):
+        """Test deducing the target from the source."""
+
+        b = SCons.Builder.Builder(action='foo', suffix='.o')
+
+        tgt = b(env, 'aaa')
+        assert str(tgt) == 'aaa.o', str(tgt)
+        assert len(tgt.sources) == 1, map(str, tgt.sources)
+        assert str(tgt.sources[0]) == 'aaa', map(str, tgt.sources)
+
+        tgt = b(env, 'bbb.c')
+        assert str(tgt) == 'bbb.o', str(tgt)
+        assert len(tgt.sources) == 1, map(str, tgt.sources)
+        assert str(tgt.sources[0]) == 'bbb.c', map(str, tgt.sources)
+
+        tgt = b(env, 'ccc.x.c')
+        assert str(tgt) == 'ccc.x.o', str(tgt)
+        assert len(tgt.sources) == 1, map(str, tgt.sources)
+        assert str(tgt.sources[0]) == 'ccc.x.c', map(str, tgt.sources)
+
+        tgt = b(env, ['d0.c', 'd1.c'])
+        assert len(tgt) == 2, map(str, tgt)
+        assert str(tgt[0]) == 'd0.o', map(str, tgt)
+        assert str(tgt[1]) == 'd1.o', map(str, tgt)
+        assert len(tgt[0].sources) == 2,  map(str, tgt[0].sources)
+        assert str(tgt[0].sources[0]) == 'd0.c', map(str, tgt[0].sources)
+        assert str(tgt[0].sources[1]) == 'd1.c', map(str, tgt[0].sources)
+        assert len(tgt[1].sources) == 2,  map(str, tgt[1].sources)
+        assert str(tgt[1].sources[0]) == 'd0.c', map(str, tgt[1].sources)
+        assert str(tgt[1].sources[1]) == 'd1.c', map(str, tgt[1].sources)
+
+        tgt = b(env, source='eee')
+        assert str(tgt) == 'eee.o', str(tgt)
+        assert len(tgt.sources) == 1, map(str, tgt.sources)
+        assert str(tgt.sources[0]) == 'eee', map(str, tgt.sources)
+
+        tgt = b(env, source='fff.c')
+        assert str(tgt) == 'fff.o', str(tgt)
+        assert len(tgt.sources) == 1, map(str, tgt.sources)
+        assert str(tgt.sources[0]) == 'fff.c', map(str, tgt.sources)
+
+        tgt = b(env, source='ggg.x.c')
+        assert str(tgt) == 'ggg.x.o', str(tgt)
+        assert len(tgt.sources) == 1, map(str, tgt.sources)
+        assert str(tgt.sources[0]) == 'ggg.x.c', map(str, tgt.sources)
+
+        tgt = b(env, source=['h0.c', 'h1.c'])
+        assert len(tgt) == 2, map(str, tgt)
+        assert str(tgt[0]) == 'h0.o', map(str, tgt)
+        assert str(tgt[1]) == 'h1.o', map(str, tgt)
+        assert len(tgt[0].sources) == 2,  map(str, tgt[0].sources)
+        assert str(tgt[0].sources[0]) == 'h0.c', map(str, tgt[0].sources)
+        assert str(tgt[0].sources[1]) == 'h1.c', map(str, tgt[0].sources)
+        assert len(tgt[1].sources) == 2,  map(str, tgt[1].sources)
+        assert str(tgt[1].sources[0]) == 'h0.c', map(str, tgt[1].sources)
+        assert str(tgt[1].sources[1]) == 'h1.c', map(str, tgt[1].sources)
+
+
 if __name__ == "__main__":
     suite = unittest.makeSuite(BuilderTestCase, 'test_')
     if not unittest.TextTestRunner().run(suite).wasSuccessful():
index 30e0a536b57ce07f05da91cc56c3b9f27cbf0556..42aa326fc101e4ca34f1e6d237536541900c6ef2 100644 (file)
@@ -212,9 +212,8 @@ class Environment:
                 self.env = env
                 self.builder = builder
 
-            def __call__(self, target = None, source = None, **kw):
-                return apply(self.builder, (self.env, target, source),
-                             kw)
+            def __call__(self, *args, **kw):
+                return apply(self.builder, (self.env,) + args, kw)
 
             # This allows a Builder to be executed directly
             # through the Environment to which it's attached.
@@ -356,6 +355,25 @@ class Environment:
         if len(ret) == 1:
             ret = ret[0]
         return ret
+
+    def SideEffect(self, side_effect, target):
+        """Tell scons that side_effects are built as side 
+        effects of building targets."""
+        side_effects = SCons.Node.arg2nodes(side_effect, self.fs.File)
+        targets = SCons.Node.arg2nodes(target, self.fs.File)
+
+        for side_effect in side_effects:
+            if side_effect.builder is not None:
+                raise UserError, "Multiple ways to build the same target were specified for: %s" % str(side_effect)
+            side_effect.add_source(targets)
+            side_effect.side_effect = 1
+            self.Precious(side_effect)
+            for target in targets:
+                target.side_effects.append(side_effect)
+        if len(side_effects) == 1:
+            return side_effects[0]
+        else:
+            return side_effects
   
     def subst(self, string):
        """Recursively interpolates construction variables from the
index 2e7003ac1ee3e08a13091c1c1bbdc3cd8b3f6969..75e5e83c7569629f55289013c857e22527714e0f 100644 (file)
@@ -52,6 +52,7 @@ def diff_env(env1, env2):
     s2 = s2 + "}\n"
     return s1 + s2
 
+called_it = {}
 built_it = {}
 
 class Builder:
@@ -59,10 +60,15 @@ class Builder:
     a target is simply setting a value in the dictionary.
     """
     def __init__(self, name = None):
-       self.name = name
+        self.name = name
+
+    def __call__(self, env, **kw):
+        global called_it
+        called_it.update(kw)
 
     def execute(self, target = None, **kw):
-       built_it[target] = 1
+        global built_it
+        built_it[target] = 1
 
 
 
@@ -86,7 +92,36 @@ class Scanner:
 
 class EnvironmentTestCase(unittest.TestCase):
 
-    def test_Builders(self):
+    def test_Builder_calls(self):
+        """Test Builder calls through different environments
+        """
+        global called_it
+
+        b1 = Builder()
+        b2 = Builder()
+
+        env = Environment()
+        env.Replace(BUILDERS = { 'builder1' : b1,
+                                 'builder2' : b2 })
+        called_it = {}
+        env.builder1(target = 'out1')
+        assert called_it['target'] == 'out1', called_it
+        assert not called_it.has_key('source')
+
+        called_it = {}
+        env.builder2(target = 'out2', xyzzy = 1)
+        assert called_it['target'] == 'out2', called_it
+        assert called_it['xyzzy'] == 1, called_it
+        assert not called_it.has_key('source')
+
+        called_it = {}
+        env.builder1(foo = 'bar')
+        assert called_it['foo'] == 'bar', called_it
+        assert not called_it.has_key('target')
+        assert not called_it.has_key('source')
+
+
+    def test_Builder_execs(self):
        """Test Builder execution through different environments
 
        One environment is initialized with a single
@@ -372,6 +407,18 @@ class EnvironmentTestCase(unittest.TestCase):
         assert 'foo1.in' in map(lambda x: x.path, t.sources)
         assert 'foo2.in' in map(lambda x: x.path, t.sources)
 
+    def test_SideEffect(self):
+        """Test the SideEffect() method"""
+        env = Environment()
+        foo = env.Object('foo.obj', 'foo.cpp')
+        bar = env.Object('bar.obj', 'bar.cpp')
+        s = env.SideEffect('mylib.pdb', ['foo.obj', 'bar.obj'])
+        assert s.side_effect
+        assert foo.side_effects == [s]
+        assert bar.side_effects == [s]
+        assert s.depends_on([bar])
+        assert s.depends_on([foo])
+
     def test_subst(self):
        """Test substituting construction variables within strings