From f1ccb4d7a026f658c80a058827bda351394a01bc Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Mon, 8 Dec 2003 04:43:08 +0000 Subject: Relax the duplicate-environment restriction for targets so that it's okay if the two environments would build the target with the same command or function call. (Scott Fritchie) --- src/CHANGES.txt | 6 + src/engine/SCons/Builder.py | 12 +- src/engine/SCons/Script/__init__.py | 1 + src/engine/SCons/Warnings.py | 3 + test/multi.py | 247 ++++++++++++++++++++++++++++-------- test/option--warn.py | 41 ++++++ 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() -- cgit v0.12