Merged revisions 1440-1541,1543-1582,1584-1667,1669-1675,1677-1738 via svnmerge from
[scons.git] / test / multi.py
1 #!/usr/bin/env python
2 #
3 # __COPYRIGHT__
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
12 #
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 #
24
25 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
26
27 """
28 Test various cases where a target is "built" by multiple builder calls.
29 """
30
31 import os.path
32 import string
33
34 import TestCmd
35 import TestSCons
36
37 test = TestSCons.TestSCons(match=TestCmd.match_re)
38
39 _python_ = TestSCons._python_
40
41 #
42 # A builder with "multi" set can be called multiple times and
43 # the source files are added to the list.
44 #
45
46 test.write('SConstruct', """
47 def build(env, target, source):
48     file = open(str(target[0]), 'wb')
49     for s in source:
50         file.write(open(str(s), 'rb').read())
51
52 B = Builder(action=build, multi=1)
53 env = Environment(BUILDERS = { 'B' : B })
54 env.B(target = 'file1.out', source = 'file1a.in')
55 env.B(target = 'file1.out', source = 'file1b.in')
56 """)
57
58 test.write('file1a.in', 'file1a.in\n')
59 test.write('file1b.in', 'file1b.in\n')
60
61 test.run(arguments='file1.out')
62
63 test.must_match('file1.out', "file1a.in\nfile1b.in\n")
64
65
66 #
67 # A builder with "multi" not set generates an error on the second call.
68 #
69
70 test.write('SConstruct', """
71 def build(env, target, source):
72     file = open(str(target[0]), 'wb')
73     for s in source:
74         file.write(open(str(s), 'rb').read())
75
76 B = Builder(action=build, multi=0)
77 env = Environment(BUILDERS = { 'B' : B })
78 env.B(target = 'file2.out', source = 'file2a.in')
79 env.B(target = 'file2.out', source = 'file2b.in')
80 """)
81
82 test.write('file2a.in', 'file2a.in\n')
83 test.write('file2b.in', 'file2b.in\n')
84
85 test.run(arguments='file2.out', 
86          status=2, 
87          stderr=TestSCons.re_escape("""
88 scons: *** Multiple ways to build the same target were specified for: file2.out  (from ['file2a.in'] and from ['file2b.in'])
89 """) + TestSCons.file_expr)
90
91
92 #
93 # A warning is generated if the calls have different overrides but the
94 # overrides don't appear to affect the build operation.
95 #
96
97 test.write('SConstruct', """
98 def build(env, target, source):
99     file = open(str(target[0]), 'wb')
100     for s in source:
101         file.write(open(str(s), 'rb').read())
102
103 B = Builder(action=build, multi=1)
104 env = Environment(BUILDERS = { 'B' : B })
105 env.B(target = 'file3.out', source = 'file3a.in', foo=1)
106 env.B(target = 'file3.out', source = 'file3b.in', foo=2)
107 """)
108
109 test.write('file3a.in', 'file3a.in\n')
110 test.write('file3b.in', 'file3b.in\n')
111
112 test.run(arguments='file3.out', 
113          stderr=TestSCons.re_escape("""
114 scons: warning: Two different environments were specified for target file3.out,
115 \tbut they appear to have the same action: build(target, source, env)
116 """) + TestSCons.file_expr)
117
118 #
119 # A warning is generated if the calls have different overrides but the
120 # overrides don't appear to affect the build operation.
121 #
122
123 test.write('build.py',r"""#!/usr/bin/env python
124 import sys
125 def build(num, target, source):
126     file = open(str(target), 'wb')
127     file.write('%s\n'%num)
128     for s in source:
129         file.write(open(str(s), 'rb').read())
130 build(sys.argv[1],sys.argv[2],sys.argv[3:])
131 """)
132
133 test.write('SConstruct', """
134
135 B = Builder(action='%(_python_)s build.py $foo $TARGET $SOURCES', multi=1)
136 env = Environment(BUILDERS = { 'B' : B })
137 env.B(target = 'file03.out', source = 'file03a.in', foo=1)
138 env.B(target = 'file03.out', source = 'file03b.in', foo=2)
139 """ % locals())
140
141 test.write('file03a.in', 'file03a.in\n')
142 test.write('file03b.in', 'file03b.in\n')
143
144 test.run(arguments='file03.out', 
145          status=2, 
146          stderr=TestSCons.re_escape("""
147 scons: *** Two environments with different actions were specified for the same target: file03.out
148 """) + TestSCons.file_expr)
149
150 #
151 # Everything works if the two calls have the same overrides.
152 #
153
154 test.write('build.py',r"""#!/usr/bin/env python
155 import sys
156 def build(num, target, source):
157     file = open(str(target), 'wb')
158     file.write('%s\n'%num)
159     for s in source:
160         file.write(open(str(s), 'rb').read())
161 build(sys.argv[1],sys.argv[2],sys.argv[3:])
162 """)
163
164 test.write('SConstruct', """
165
166 B = Builder(action='%(_python_)s build.py $foo $TARGET $SOURCES', multi=1)
167 env = Environment(BUILDERS = { 'B' : B })
168 env.B(target = 'file4.out', source = 'file4a.in', foo=3)
169 env.B(target = 'file4.out', source = 'file4b.in', foo=3)
170 """ % locals())
171
172 test.write('file4a.in', 'file4a.in\n')
173 test.write('file4b.in', 'file4b.in\n')
174
175 python_expr = string.replace(TestSCons.python, '\\', '\\\\')
176 act = TestSCons.re_escape('"%s" build.py \$foo \$TARGET \$SOURCES' % python_expr)
177
178 test.run(arguments='file4.out', 
179          stderr=("""
180 scons: warning: Two different environments were specified for target file4.out,
181 \tbut they appear to have the same action: %s
182 """ % act) + TestSCons.file_expr)
183
184 test.must_match('file4.out', "3\nfile4a.in\nfile4b.in\n")
185
186
187 #
188 # Two different environments can be used for the same target, so long
189 # as the actions have the same signature; a warning is generated.
190 #
191
192 test.write('SConstruct', """
193 def build(env, target, source):
194     file = open(str(target[0]), 'wb')
195     for s in source:
196         file.write(open(str(s), 'rb').read())
197
198 B = Builder(action=build, multi=1)
199 env = Environment(BUILDERS = { 'B' : B })
200 env2 = env.Clone(DIFFERENT_VARIABLE = 'true')
201 env.B(target = 'file5.out', source = 'file5a.in')
202 env2.B(target = 'file5.out', source = 'file5b.in')
203 """)
204
205 test.write('file5a.in', 'file5a.in\n')
206 test.write('file5b.in', 'file5b.in\n')
207
208 test.run(arguments='file5.out', 
209          stderr=TestSCons.re_escape("""
210 scons: warning: Two different environments were specified for target file5.out,
211 \tbut they appear to have the same action: build(target, source, env)
212 """) + TestSCons.file_expr)
213
214 test.must_match('file5.out', "file5a.in\nfile5b.in\n")
215
216
217 #
218 # Environments with actions that have different signatures generate
219 # an error.
220 #
221
222 test.write('SConstruct', """
223 def build(env, target, source):
224     file = open(str(target[0]), 'wb')
225     for s in source:
226         file.write(open(str(s), 'rb').read())
227
228 B = Builder(action=Action(build, varlist=['XXX']), multi=1)
229 env = Environment(BUILDERS = { 'B' : B }, XXX = 'foo')
230 env2 = env.Clone(XXX = 'var')
231 env.B(target = 'file6.out', source = 'file6a.in')
232 env2.B(target = 'file6.out', source = 'file6b.in')
233 """)
234
235 test.write('file6a.in', 'file6a.in\n')
236 test.write('file6b.in', 'file6b.in\n')
237
238 test.run(arguments='file6.out', 
239          status=2,
240          stderr=TestSCons.re_escape("""
241 scons: *** Two environments with different actions were specified for the same target: file6.out
242 """) + TestSCons.file_expr)
243
244
245 #
246 # A builder without "multi" set can still be called multiple times
247 # if the calls are the same.
248 #
249
250 test.write('SConstruct', """
251 def build(env, target, source):
252     file = open(str(target[0]), 'wb')
253     for s in source:
254         file.write(open(str(s), 'rb').read())
255
256 B = Builder(action=build, multi=0)
257 env = Environment(BUILDERS = { 'B' : B })
258 env.B(target = 'file7.out', source = 'file7.in')
259 env.B(target = 'file7.out', source = 'file7.in')
260 """)
261
262 test.write('file7.in', 'file7.in\n')
263
264 test.run(arguments='file7.out')
265
266 test.must_match('file7.out', "file7.in\n")
267
268
269 #
270 # Trying to call a target with two different "multi" builders
271 # generates an error.
272 #
273
274 test.write('SConstruct', """
275 def build(env, target, source):
276     file = open(str(target[0]), 'wb')
277     for s in source:
278         file.write(open(str(s), 'rb').read())
279
280 def build2(env, target, source):
281     build(env, target, source)
282
283 B = Builder(action=build, multi=1)
284 C = Builder(action=build2, multi=1)
285 env = Environment(BUILDERS = { 'B' : B, 'C' : C })
286 env.B(target = 'file8.out', source = 'file8.in')
287 env.C(target = 'file8.out', source = 'file8.in')
288 """)
289
290 test.write('file8a.in', 'file8a.in\n')
291 test.write('file8b.in', 'file8b.in\n')
292
293 test.run(arguments='file8.out', 
294          status=2, 
295          stderr=TestSCons.re_escape("""
296 scons: *** Two different builders (B and C) were specified for the same target: file8.out
297 """) + TestSCons.file_expr)
298
299
300 #
301 # A "multi" builder can be called multiple times with the same target list
302 # if everything is identical.
303 #
304
305 test.write('SConstruct', """
306 def build(env, target, source):
307     for t in target:
308         file = open(str(t), 'wb')
309         for s in source:
310             file.write(open(str(s), 'rb').read())
311
312 B = Builder(action=build, multi=1)
313 env = Environment(BUILDERS = { 'B' : B })
314 env.B(target = ['file9a.out', 'file9b.out'], source = 'file9a.in')
315 env.B(target = ['file9a.out', 'file9b.out'], source = 'file9b.in')
316 """)
317
318 test.write('file9a.in', 'file9a.in\n')
319 test.write('file9b.in', 'file9b.in\n')
320
321 test.run(arguments='file9b.out')
322
323 test.must_match('file9a.out', "file9a.in\nfile9b.in\n")
324 test.must_match('file9b.out', "file9a.in\nfile9b.in\n")
325
326
327 #
328 # A "multi" builder can NOT be called multiple times with target lists
329 # that have different orders.  This is intentional; the order of the
330 # targets matter to the builder because the build command can contain
331 # things like ${TARGET[0]}.
332 #
333
334 test.write('SConstruct', """
335 def build(env, target, source):
336     for t in target:
337         file = open(str(target[0]), 'wb')
338         for s in source:
339             file.write(open(str(s), 'rb').read())
340
341 B = Builder(action=build, multi=1)
342 env = Environment(BUILDERS = { 'B' : B })
343 env.B(target = ['file10a.out', 'file10b.out'], source = 'file10.in')
344 env.B(target = ['file10b.out', 'file10a.out'], source = 'file10.in')
345 """)
346
347 test.write('file10.in', 'file10.in\n')
348
349 test.run(arguments='file10.out', 
350          status=2, 
351          stderr=TestSCons.re_escape("""
352 scons: *** Two different target sets have a target in common: file10b.out
353 """) + TestSCons.file_expr)
354
355
356 #
357 # A target file can't be in two different target lists.
358 #
359
360 # XXX It would be nice if the following two tests could be made to work
361 # by executing the action once for each unique set of targets. This
362 # would make it simple to deal with PDB files on Windows like so:
363 #
364 #     env.Object(['foo.obj', 'vc60.pdb'], 'foo.c')
365 #     env.Object(['bar.obj', 'vc60.pdb'], 'bar.c')
366
367 test.write('SConstruct', """
368 def build(env, target, source):
369     for t in target:
370         file = open(str(target[0]), 'wb')
371         for s in source:
372             file.write(open(str(s), 'rb').read())
373
374 B = Builder(action=build, multi=1)
375 env = Environment(BUILDERS = { 'B' : B })
376 env.B(target = ['file11a.out', 'file11b.out'], source = 'file11a.in')
377 env.B(target = ['file11b.out', 'file11c.out'], source = 'file11b.in')
378 """)
379
380 test.write('file11a.in', 'file11a.in\n')
381 test.write('file11b.in', 'file11b.in\n')
382
383 test.run(arguments='file11.out', 
384          status=2, 
385          stderr=TestSCons.re_escape("""
386 scons: *** Two different target sets have a target in common: file11b.out
387 """) + TestSCons.file_expr)
388
389
390 #
391 # A target file can't be a lone target and in a list.
392 #
393
394 test.write('SConstruct', """
395 def build(env, target, source):
396     for t in target:
397         file = open(str(target[0]), 'wb')
398         for s in source:
399             file.write(open(str(s), 'rb').read())
400
401 B = Builder(action=build, multi=1)
402 env = Environment(BUILDERS = { 'B' : B })
403 env.B(target = ['file12a.out', 'file12b.out'], source = 'file12a.in')
404 env.B(target = 'file12a.out', source = 'file12b.in')
405 """)
406
407 test.write('file12a.in', 'file12a.in\n')
408 test.write('file12b.in', 'file12b.in\n')
409
410 test.run(arguments='file12.out', 
411          status=2, 
412          stderr=TestSCons.re_escape("""
413 scons: *** Cannot build same target `file12a.out' as singular and list
414 """) + TestSCons.file_expr)
415
416
417
418 test.pass_test()