summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/CHANGES.txt6
-rw-r--r--src/engine/SCons/Builder.py12
-rw-r--r--src/engine/SCons/Script/__init__.py1
-rw-r--r--src/engine/SCons/Warnings.py3
-rw-r--r--test/multi.py247
-rw-r--r--test/option--warn.py41
6 files changed, 255 insertions, 55 deletions
diff --git a/src/CHANGES.txt b/src/CHANGES.txt
index a4e4c60..391d11b 100644
--- a/src/CHANGES.txt
+++ b/src/CHANGES.txt
@@ -61,6 +61,12 @@ RELEASE 0.95 - XXX
- Make the message about ignoring a missing SConscript file into a
suppressable Warning, not a hard-coded sys.stderr.write().
+ - If a builder can be called multiple times for a target (because
+ the sources and overrides are identical, or it's a builder with the
+ "multi" flag set), allow the builder to be called through multiple
+ environments so long as the builders have the same signature for
+ the environments in questions (that is, they're the same action).
+
From Steven Knight:
- Fix EnsureSConsVersion() so it checks against the SCons version,
diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py
index 17d661c..8372292 100644
--- a/src/engine/SCons/Builder.py
+++ b/src/engine/SCons/Builder.py
@@ -164,9 +164,19 @@ def _init_nodes(builder, env, overrides, tlist, slist):
raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
if t.has_builder():
if t.env != env:
- raise UserError, "Two different environments were specified for the same target: %s"%str(t)
+ t_contents = t.builder.action.get_contents(tlist, slist, t.env)
+ contents = t.builder.action.get_contents(tlist, slist, env)
+
+ if t_contents == contents:
+ SCons.Warnings.warn(SCons.Warnings.DuplicateEnvironmentWarning,
+ "Two different environments were specified for target %s,\n\tbut they appear to have the same action: %s"%(str(t), t.builder.action.strfunction(tlist, slist, t.env)))
+
+ else:
+ raise UserError, "Two environments with different actions were specified for the same target: %s"%str(t)
+
elif t.overrides != overrides:
raise UserError, "Two different sets of overrides were specified for the same target: %s"%str(t)
+
elif builder.scanner and t.target_scanner and builder.scanner != t.target_scanner:
raise UserError, "Two different scanners were specified for the same target: %s"%str(t)
diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py
index 0e56214..ac05f3c 100644
--- a/src/engine/SCons/Script/__init__.py
+++ b/src/engine/SCons/Script/__init__.py
@@ -709,6 +709,7 @@ def _main(args, parser):
SCons.Warnings._warningOut = _scons_internal_warning
SCons.Warnings.enableWarningClass(SCons.Warnings.CorruptSConsignWarning)
SCons.Warnings.enableWarningClass(SCons.Warnings.DeprecatedWarning)
+ SCons.Warnings.enableWarningClass(SCons.Warnings.DuplicateEnvironmentWarning)
SCons.Warnings.enableWarningClass(SCons.Warnings.MissingSConscriptWarning)
SCons.Warnings.enableWarningClass(SCons.Warnings.NoParallelSupportWarning)
diff --git a/src/engine/SCons/Warnings.py b/src/engine/SCons/Warnings.py
index 6426830..2e2e525 100644
--- a/src/engine/SCons/Warnings.py
+++ b/src/engine/SCons/Warnings.py
@@ -48,6 +48,9 @@ class DependencyWarning(Warning):
class DeprecatedWarning(Warning):
pass
+class DuplicateEnvironmentWarning(Warning):
+ pass
+
class MissingSConscriptWarning(Warning):
pass
diff --git a/test/multi.py b/test/multi.py
index 1974a5f..9920cbc 100644
--- a/test/multi.py
+++ b/test/multi.py
@@ -24,11 +24,21 @@
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+"""
+Test various cases where a target is "built" by multiple builder calls.
+"""
+
import TestSCons
import os.path
test = TestSCons.TestSCons()
+
+#
+# A builder with "multi" set can be called multiple times and
+# the source files are added to the list.
+#
+
test.write('SConstruct', """
def build(env, target, source):
file = open(str(target[0]), 'wb')
@@ -37,17 +47,21 @@ def build(env, target, source):
B = Builder(action=build, multi=1)
env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'foo.out', source = 'foo.in')
-env.B(target = 'foo.out', source = 'bar.in')
+env.B(target = 'file1.out', source = 'file1a.in')
+env.B(target = 'file1.out', source = 'file1b.in')
""")
-test.write('foo.in', 'foo.in\n')
-test.write('bar.in', 'bar.in\n')
+test.write('file1a.in', 'file1a.in\n')
+test.write('file1b.in', 'file1b.in\n')
+
+test.run(arguments='file1.out')
-test.run(arguments='foo.out')
+test.fail_test(not test.read('file1.out') == 'file1a.in\nfile1b.in\n')
-test.fail_test(not os.path.exists(test.workpath('foo.out')))
-test.fail_test(not test.read('foo.out') == 'foo.in\nbar.in\n')
+
+#
+# A builder with "multi" not set generates an error on the second call.
+#
test.write('SConstruct', """
def build(env, target, source):
@@ -57,17 +71,26 @@ def build(env, target, source):
B = Builder(action=build, multi=0)
env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'foo.out', source = 'foo.in')
-env.B(target = 'foo.out', source = 'bar.in')
+env.B(target = 'file2.out', source = 'file2a.in')
+env.B(target = 'file2.out', source = 'file2b.in')
""")
-test.run(arguments='foo.out',
+test.write('file2a.in', 'file2a.in\n')
+test.write('file2b.in', 'file2b.in\n')
+
+test.run(arguments='file2.out',
status=2,
stderr="""
-scons: *** Multiple ways to build the same target were specified for: foo.out
+scons: *** Multiple ways to build the same target were specified for: file2.out
File "SConstruct", line 10, in ?
""")
+
+#
+# The second call generates an error if the two calls have different
+# overrides.
+#
+
test.write('SConstruct', """
def build(env, target, source):
file = open(str(target[0]), 'wb')
@@ -76,17 +99,25 @@ def build(env, target, source):
B = Builder(action=build, multi=1)
env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'foo.out', source = 'foo.in', foo=1)
-env.B(target = 'foo.out', source = 'bar.in', foo=2)
+env.B(target = 'file3.out', source = 'file3a.in', foo=1)
+env.B(target = 'file3.out', source = 'file3b.in', foo=2)
""")
-test.run(arguments='foo.out',
+test.write('file3a.in', 'file3a.in\n')
+test.write('file3b.in', 'file3b.in\n')
+
+test.run(arguments='file3.out',
status=2,
stderr="""
-scons: *** Two different sets of overrides were specified for the same target: foo.out
+scons: *** Two different sets of overrides were specified for the same target: file3.out
File "SConstruct", line 10, in ?
""")
+
+#
+# Everything works if the two calls have the same overrides.
+#
+
test.write('SConstruct', """
def build(env, target, source):
file = open(str(target[0]), 'wb')
@@ -95,18 +126,83 @@ def build(env, target, source):
B = Builder(action=build, multi=1)
env = Environment(BUILDERS = { 'B' : B })
-env2 = env.Copy(CCFLAGS='foo')
-env.B(target = 'foo.out', source = 'foo.in')
-env2.B(target = 'foo.out', source = 'bar.in')
+env.B(target = 'file4.out', source = 'file4a.in', foo=3)
+env.B(target = 'file4.out', source = 'file4b.in', foo=3)
""")
-test.run(arguments='foo.out',
- status=2,
+test.write('file4a.in', 'file4a.in\n')
+test.write('file4b.in', 'file4b.in\n')
+
+test.run(arguments='file4.out')
+
+test.fail_test(not test.read('file4.out') == 'file4a.in\nfile4b.in\n')
+
+
+#
+# Two different environments can be used for the same target, so long
+# as the actions have the same signature; a warning is generated.
+#
+
+test.write('SConstruct', """
+def build(env, target, source):
+ file = open(str(target[0]), 'wb')
+ for s in source:
+ file.write(open(str(s), 'rb').read())
+
+B = Builder(action=build, multi=1)
+env = Environment(BUILDERS = { 'B' : B })
+env2 = env.Copy(DIFFERENT_VARIABLE = 'true')
+env.B(target = 'file5.out', source = 'file5a.in')
+env2.B(target = 'file5.out', source = 'file5b.in')
+""")
+
+test.write('file5a.in', 'file5a.in\n')
+test.write('file5b.in', 'file5b.in\n')
+
+test.run(arguments='file5.out',
stderr="""
-scons: *** Two different environments were specified for the same target: foo.out
+scons: warning: Two different environments were specified for target file5.out,
+ but they appear to have the same action: build("file5.out", "file5b.in")
File "SConstruct", line 11, in ?
""")
+test.fail_test(not test.read('file5.out') == 'file5a.in\nfile5b.in\n')
+
+
+#
+# Environments with actions that have different signatures generate
+# an error.
+#
+
+test.write('SConstruct', """
+def build(env, target, source):
+ file = open(str(target[0]), 'wb')
+ for s in source:
+ file.write(open(str(s), 'rb').read())
+
+B = Builder(action=Action(build, varlist=['XXX']), multi=1)
+env = Environment(BUILDERS = { 'B' : B }, XXX = 'foo')
+env2 = env.Copy(XXX = 'var')
+env.B(target = 'file6.out', source = 'file6a.in')
+env2.B(target = 'file6.out', source = 'file6b.in')
+""")
+
+test.write('file6a.in', 'file6a.in\n')
+test.write('file6b.in', 'file6b.in\n')
+
+test.run(arguments='file6.out',
+ status=2,
+ stderr="""
+scons: *** Two environments with different actions were specified for the same target: file6.out
+File "SConstruct", line 11, in ?
+""")
+
+
+#
+# A builder without "multi" set can still be called multiple times
+# if the calls are the same.
+#
+
test.write('SConstruct', """
def build(env, target, source):
file = open(str(target[0]), 'wb')
@@ -115,12 +211,21 @@ def build(env, target, source):
B = Builder(action=build, multi=0)
env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'foo.out', source = 'foo.in')
-env.B(target = 'foo.out', source = 'foo.in')
+env.B(target = 'file7.out', source = 'file7.in')
+env.B(target = 'file7.out', source = 'file7.in')
""")
-test.run(arguments='foo.out')
-test.fail_test(not test.read('foo.out') == 'foo.in\n')
+test.write('file7.in', 'file7.in\n')
+
+test.run(arguments='file7.out')
+
+test.fail_test(not test.read('file7.out') == 'file7.in\n')
+
+
+#
+# Trying to call a target with two different "multi" builders
+# generates an error.
+#
test.write('SConstruct', """
def build(env, target, source):
@@ -134,17 +239,26 @@ def build2(env, target, source):
B = Builder(action=build, multi=1)
C = Builder(action=build2, multi=1)
env = Environment(BUILDERS = { 'B' : B, 'C' : C })
-env.B(target = 'foo.out', source = 'foo.in')
-env.C(target = 'foo.out', source = 'bar.in')
+env.B(target = 'file8.out', source = 'file8.in')
+env.C(target = 'file8.out', source = 'file8.in')
""")
-test.run(arguments='foo.out',
+test.write('file8a.in', 'file8a.in\n')
+test.write('file8b.in', 'file8b.in\n')
+
+test.run(arguments='file8.out',
status=2,
stderr="""
-scons: *** Two different builders (B and C) were specified for the same target: foo.out
+scons: *** Two different builders (B and C) were specified for the same target: file8.out
File "SConstruct", line 14, in ?
""")
+
+#
+# A "multi" builder can be called multiple times with the same target list
+# if everything is identical.
+#
+
test.write('SConstruct', """
def build(env, target, source):
for t in target:
@@ -154,15 +268,25 @@ def build(env, target, source):
B = Builder(action=build, multi=1)
env = Environment(BUILDERS = { 'B' : B })
-env.B(target = ['foo.out', 'bar.out'], source = 'foo.in')
-env.B(target = ['foo.out', 'bar.out'], source = 'bar.in')
+env.B(target = ['file9a.out', 'file9b.out'], source = 'file9a.in')
+env.B(target = ['file9a.out', 'file9b.out'], source = 'file9b.in')
""")
-test.run(arguments='bar.out')
-test.fail_test(not os.path.exists(test.workpath('bar.out')))
-test.fail_test(not test.read('bar.out') == 'foo.in\nbar.in\n')
-test.fail_test(not os.path.exists(test.workpath('foo.out')))
-test.fail_test(not test.read('foo.out') == 'foo.in\nbar.in\n')
+test.write('file9a.in', 'file9a.in\n')
+test.write('file9b.in', 'file9b.in\n')
+
+test.run(arguments='file9b.out')
+
+test.fail_test(not test.read('file9a.out') == 'file9a.in\nfile9b.in\n')
+test.fail_test(not test.read('file9b.out') == 'file9a.in\nfile9b.in\n')
+
+
+#
+# A "multi" builder can NOT be called multiple times with target lists
+# that have different orders. This is intentional; the order of the
+# targets matter to the builder because the build command can contain
+# things like ${TARGET[0]}.
+#
test.write('SConstruct', """
def build(env, target, source):
@@ -173,22 +297,27 @@ def build(env, target, source):
B = Builder(action=build, multi=1)
env = Environment(BUILDERS = { 'B' : B })
-env.B(target = ['foo.out', 'bar.out'], source = 'foo.in')
-env.B(target = ['bar.out', 'foo.out'], source = 'bar.in')
+env.B(target = ['file10a.out', 'file10b.out'], source = 'file10.in')
+env.B(target = ['file10b.out', 'file10a.out'], source = 'file10.in')
""")
-# This is intentional. The order of the targets matter to the
-# builder because the build command can contain things like ${TARGET[0]}:
-test.run(arguments='foo.out',
+test.write('file10.in', 'file10.in\n')
+
+test.run(arguments='file10.out',
status=2,
stderr="""
-scons: *** Two different target sets have a target in common: bar.out
+scons: *** Two different target sets have a target in common: file10b.out
File "SConstruct", line 11, in ?
""")
-# XXX It would be nice if the following two tests could be made to
-# work by executing the action once for each unique set of
-# targets. This would make it simple to deal with PDB files on Windows like so:
+
+#
+# A target file can't be in two different target lists.
+#
+
+# XXX It would be nice if the following two tests could be made to work
+# by executing the action once for each unique set of targets. This
+# would make it simple to deal with PDB files on Windows like so:
#
# env.Object(['foo.obj', 'vc60.pdb'], 'foo.c')
# env.Object(['bar.obj', 'vc60.pdb'], 'bar.c')
@@ -202,17 +331,25 @@ def build(env, target, source):
B = Builder(action=build, multi=1)
env = Environment(BUILDERS = { 'B' : B })
-env.B(target = ['foo.out', 'bar.out'], source = 'foo.in')
-env.B(target = ['bar.out', 'blat.out'], source = 'bar.in')
+env.B(target = ['file11a.out', 'file11b.out'], source = 'file11a.in')
+env.B(target = ['file11b.out', 'file11c.out'], source = 'file11b.in')
""")
-test.run(arguments='foo.out',
+test.write('file11a.in', 'file11a.in\n')
+test.write('file11b.in', 'file11b.in\n')
+
+test.run(arguments='file11.out',
status=2,
stderr="""
-scons: *** Two different target sets have a target in common: bar.out
+scons: *** Two different target sets have a target in common: file11b.out
File "SConstruct", line 11, in ?
""")
+
+#
+# A target file can't be a lone target and in a list.
+#
+
test.write('SConstruct', """
def build(env, target, source):
for t in target:
@@ -222,18 +359,20 @@ def build(env, target, source):
B = Builder(action=build, multi=1)
env = Environment(BUILDERS = { 'B' : B })
-env.B(target = ['foo.out', 'bar.out'], source = 'foo.in')
-env.B(target = 'foo.out', source = 'bar.in')
+env.B(target = ['file12a.out', 'file12b.out'], source = 'file12a.in')
+env.B(target = 'file12a.out', source = 'file12b.in')
""")
-test.run(arguments='foo.out',
+test.write('file12a.in', 'file12a.in\n')
+test.write('file12b.in', 'file12b.in\n')
+
+test.run(arguments='file12.out',
status=2,
stderr="""
-scons: *** Two different builders (ListBuilder(B) and B) were specified for the same target: foo.out
+scons: *** Two different builders (ListBuilder(B) and B) were specified for the same target: file12a.out
File "SConstruct", line 11, in ?
""")
-
test.pass_test()
diff --git a/test/option--warn.py b/test/option--warn.py
index c8e16a9..ac510bc 100644
--- a/test/option--warn.py
+++ b/test/option--warn.py
@@ -52,6 +52,8 @@ test = TestSCons.TestSCons(match = TestCmd.match_re_dotall)
#File "SConstruct", line 2, in \?
#""")
+
+
test.write("SConstruct","""
def build(target, source, env):
pass
@@ -82,6 +84,8 @@ scons: warning: No dependency generated for file: not_there\.h \(included from:
File ".+", line \d+, in .+
""")
+
+
test.write("SConstruct", """\
def build(target, source, env):
pass
@@ -99,4 +103,41 @@ File ".+", line \d+, in .+
test.run(arguments = '--warn=no-missing-sconscript .', stderr = "")
+
+
+test.write('SConstruct', """
+def build(env, target, source):
+ file = open(str(target[0]), 'wb')
+ for s in source:
+ file.write(open(str(s), 'rb').read())
+
+B = Builder(action=build, multi=1)
+env = Environment(BUILDERS = { 'B' : B })
+env2 = env.Copy(DIFFERENT_VARIABLE = 'true')
+env.B(target = 'file1.out', source = 'file1a.in')
+env2.B(target = 'file1.out', source = 'file1b.in')
+""")
+
+test.write('file1a.in', 'file1a.in\n')
+test.write('file1b.in', 'file1b.in\n')
+
+test.run(arguments='file1.out',
+ stderr=r"""
+scons: warning: Two different environments were specified for target file1.out,
+ but they appear to have the same action: build\("file1.out", "file1b.in"\)
+File "SConstruct", line \d+, in .+
+""")
+
+test.fail_test(not test.read('file1.out') == 'file1a.in\nfile1b.in\n')
+
+test.run(arguments='--warn=duplicate-environment file1.out',
+ stderr=r"""
+scons: warning: Two different environments were specified for target file1.out,
+ but they appear to have the same action: build\("file1.out", "file1b.in"\)
+File "SConstruct", line \d+, in .+
+""")
+
+test.run(arguments='--warn=no-duplicate-environment file1.out')
+
+
test.pass_test()