Fix spurious rebuilds/reinstalls of header files and circular dependencies with gener...
[scons.git] / src / engine / SCons / BuilderTests.py
1 #
2 # __COPYRIGHT__
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining
5 # a copy of this software and associated documentation files (the
6 # "Software"), to deal in the Software without restriction, including
7 # without limitation the rights to use, copy, modify, merge, publish,
8 # distribute, sublicense, and/or sell copies of the Software, and to
9 # permit persons to whom the Software is furnished to do so, subject to
10 # the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included
13 # in all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
16 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
17 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 #
23
24 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
25
26 # Define a null function for use as a builder action.
27 # Where this is defined in the file seems to affect its
28 # byte-code contents, so try to minimize changes by
29 # defining it here, before we even import anything.
30 def Func():
31     pass
32
33 import os.path
34 import sys
35 import types
36 import unittest
37
38 import TestCmd
39
40 import SCons.Action
41 import SCons.Builder
42 import SCons.Environment
43 import SCons.Errors
44
45 # Initial setup of the common environment for all tests,
46 # a temporary working directory containing a
47 # script for writing arguments to an output file.
48 #
49 # We don't do this as a setUp() method because it's
50 # unnecessary to create a separate directory and script
51 # for each test, they can just use the one.
52 test = TestCmd.TestCmd(workdir = '')
53
54 outfile = test.workpath('outfile')
55 outfile2 = test.workpath('outfile2')
56
57 show_string = None
58
59 scons_env = SCons.Environment.Environment()
60
61 env_arg2nodes_called = None
62
63 class Environment:
64     def __init__(self, **kw):
65         self.d = {}
66         self.d['SHELL'] = scons_env['SHELL']
67         self.d['SPAWN'] = scons_env['SPAWN']
68         self.d['ESCAPE'] = scons_env['ESCAPE']
69         for k, v in kw.items():
70             self.d[k] = v
71         global env_arg2nodes_called
72         env_arg2nodes_called = None
73         self.scanner = None
74     def subst(self, s):
75         if not SCons.Util.is_String(s):
76             return s
77         try:
78             if s[0] == '$':
79                 return self.d.get(s[1:], '')
80             if s[1] == '$':
81                 return s[0] + self.d.get(s[2:], '')
82         except IndexError:
83             pass
84         return self.d.get(s, s)
85     def arg2nodes(self, args, factory):
86         global env_arg2nodes_called
87         env_arg2nodes_called = 1
88         if not SCons.Util.is_List(args):
89             args = [args]
90         list = []
91         for a in args:
92             if SCons.Util.is_String(a):
93                 a = factory(a)
94             list.append(a)
95         return list
96     def get_scanner(self, ext):
97         return self.scanner
98     def Dictionary(self):
99         return {}
100     def autogenerate(self, dir=''):
101         return {}
102     def __setitem__(self, item, var):
103         self.d[item] = var
104     def __getitem__(self, item):
105         return self.d[item]
106     def has_key(self, item):
107         return self.d.has_key(item)
108     def keys(self):
109         return self.d.keys()
110     def get(self, key, value):
111         return self.d.get(key, value)
112     def Override(self, overrides):
113         env = apply(Environment, (), self.d)
114         env.d.update(overrides)
115         return env
116     def _update(self, dict):
117         self.d.update(dict)
118     def items(self):
119         return self.d.items()
120     def sig_dict(self):
121         d = {}
122         for k,v in self.items(): d[k] = v
123         d['TARGETS'] = ['__t1__', '__t2__', '__t3__', '__t4__', '__t5__', '__t6__']
124         d['TARGET'] = d['TARGETS'][0]
125         d['SOURCES'] = ['__s1__', '__s2__', '__s3__', '__s4__', '__s5__', '__s6__']
126         d['SOURCE'] = d['SOURCES'][0]
127         return d
128
129 class MyNode_without_target_from_source:
130     def __init__(self, name):
131         self.name = name
132         self.sources = []
133         self.builder = None
134         self.side_effect = 0
135         self.source_scanner = None
136         self.backup_source_scanner = None
137     def __str__(self):
138         return self.name
139     def builder_set(self, builder):
140         self.builder = builder
141     def has_builder(self):
142         return not self.builder is None
143     def env_set(self, env, safe=0):
144         self.env = env
145     def add_source(self, source):
146         self.sources.extend(source)
147     def scanner_key(self):
148         return self.name
149     def is_derived(self):
150         return self.has_builder()
151     def generate_build_env(self, env):
152         return env
153     def get_build_env(self):
154         return self.executor.get_build_env()
155     def set_executor(self, executor):
156         self.executor = executor
157     def get_executor(self, create=1):
158         return self.executor
159
160 class MyNode(MyNode_without_target_from_source):
161     def target_from_source(self, prefix, suffix, stripext):
162         return MyNode(prefix + stripext(str(self))[0] + suffix)
163
164 class BuilderTestCase(unittest.TestCase):
165
166     def test__init__(self):
167         """Test simple Builder creation
168         """
169         builder = SCons.Builder.Builder(action="foo")
170         assert not builder is None, builder
171         builder = SCons.Builder.Builder(action="foo", OVERRIDE='x')
172         x = builder.overrides['OVERRIDE']
173         assert x == 'x', x
174
175     def test__nonzero__(self):
176         """Test a builder raising an exception when __nonzero__ is called
177         """
178         builder = SCons.Builder.Builder(action="foo")
179         exc_caught = None
180         try:
181             builder.__nonzero__()
182         except SCons.Errors.InternalError:
183             exc_caught = 1
184         assert exc_caught, "did not catch expected InternalError exception"
185
186         class Node:
187              pass
188
189         n = Node()
190         n.builder = builder
191         exc_caught = None
192         try:
193             if n.builder:
194                 pass
195         except SCons.Errors.InternalError:
196             exc_caught = 1
197         assert exc_caught, "did not catch expected InternalError exception"
198
199     def test__call__(self):
200         """Test calling a builder to establish source dependencies
201         """
202         env = Environment()
203         builder = SCons.Builder.Builder(action="foo",
204                                         target_factory=MyNode,
205                                         source_factory=MyNode)
206
207         n1 = MyNode("n1");
208         n2 = MyNode("n2");
209         builder(env, target = n1, source = n2)
210         assert env_arg2nodes_called
211         assert n1.env == env, n1.env
212         assert n1.builder == builder, n1.builder
213         assert n1.sources == [n2], n1.sources
214         assert n1.executor, "no executor found"
215         assert not hasattr(n2, 'env')
216
217         target = builder(env, target = 'n3', source = 'n4')
218         assert target.name == 'n3'
219         assert target.sources[0].name == 'n4'
220
221         target = builder(env, target = 'n4 n5', source = ['n6 n7'])
222         assert target.name == 'n4 n5'
223         assert target.sources[0].name == 'n6 n7'
224
225         target = builder(env, target = ['n8 n9'], source = 'n10 n11')
226         assert target.name == 'n8 n9'
227         assert target.sources[0].name == 'n10 n11'
228
229         # A test to be uncommented when we freeze the environment
230         # as part of calling the builder.
231         #env1 = Environment(VAR='foo')
232         #target = builder(env1, target = 'n12', source = 'n13')
233         #env1['VAR'] = 'bar'
234         #be = target.get_build_env()
235         #assert be['VAR'] == 'foo', be['VAR']
236
237         if not hasattr(types, 'UnicodeType'):
238             uni = str
239         else:
240             uni = unicode
241
242         target = builder(env, target = uni('n12 n13'),
243                           source = [uni('n14 n15')])
244         assert target.name == uni('n12 n13')
245         assert target.sources[0].name == uni('n14 n15')
246
247         target = builder(env, target = [uni('n16 n17')],
248                          source = uni('n18 n19'))
249         assert target.name == uni('n16 n17')
250         assert target.sources[0].name == uni('n18 n19')
251
252         n20 = MyNode_without_target_from_source('n20')
253         flag = 0
254         try:
255             target = builder(env, source=n20)
256         except SCons.Errors.UserError, e:
257             flag = 1
258         assert flag, "UserError should be thrown if a source node can't create a target."
259
260         builder = SCons.Builder.Builder(action="foo",
261                                         target_factory=MyNode,
262                                         source_factory=MyNode,
263                                         prefix='p-',
264                                         suffix='.s')
265         target = builder(env, source='n21')
266         assert target.name == 'p-n21.s', target
267
268     def test_mistaken_variables(self):
269         """Test keyword arguments that are often mistakes
270         """
271         import SCons.Warnings
272         env = Environment()
273         builder = SCons.Builder.Builder(action="foo")
274
275         save_warn = SCons.Warnings.warn
276         warned = []
277         def my_warn(exception, warning, warned=warned):
278             warned.append(warning)
279         SCons.Warnings.warn = my_warn
280
281         try:
282             target = builder(env, 'mistaken1', sources='mistaken1.c')
283             assert warned == ["Did you mean to use `source' instead of `sources'?"], warned
284             del warned[:]
285
286             target = builder(env, 'mistaken2', targets='mistaken2.c')
287             assert warned == ["Did you mean to use `target' instead of `targets'?"], warned
288             del warned[:]
289
290             target = builder(env, 'mistaken3', targets='mistaken3', sources='mistaken3.c')
291             assert "Did you mean to use `source' instead of `sources'?" in warned, warned
292             assert "Did you mean to use `target' instead of `targets'?" in warned, warned
293             del warned[:]
294         finally:
295             SCons.Warnings.warn = save_warn
296
297     def test_action(self):
298         """Test Builder creation
299
300         Verify that we can retrieve the supplied action attribute.
301         """
302         builder = SCons.Builder.Builder(action="foo")
303         assert builder.action.cmd_list == "foo"
304
305         def func():
306             pass
307         builder = SCons.Builder.Builder(action=func)
308         assert isinstance(builder.action, SCons.Action.FunctionAction)
309         # Preserve the following so that the baseline test will fail.
310         # Remove it in favor of the previous test at some convenient
311         # point in the future.
312         assert builder.action.execfunction == func
313
314     def test_generator(self):
315         """Test Builder creation given a generator function."""
316
317         def generator():
318             pass
319
320         builder = SCons.Builder.Builder(generator=generator)
321         assert builder.action.generator == generator
322
323     def test_cmp(self):
324         """Test simple comparisons of Builder objects
325         """
326         b1 = SCons.Builder.Builder(src_suffix = '.o')
327         b2 = SCons.Builder.Builder(src_suffix = '.o')
328         assert b1 == b2
329         b3 = SCons.Builder.Builder(src_suffix = '.x')
330         assert b1 != b3
331         assert b2 != b3
332
333     def test_target_factory(self):
334         """Test a Builder that creates target nodes of a specified class
335         """
336         class Foo:
337             pass
338         def FooFactory(target):
339             global Foo
340             return Foo(target)
341         builder = SCons.Builder.Builder(target_factory = FooFactory)
342         assert builder.target_factory is FooFactory
343         assert not builder.source_factory is FooFactory
344
345     def test_source_factory(self):
346         """Test a Builder that creates source nodes of a specified class
347         """
348         class Foo:
349             pass
350         def FooFactory(source):
351             global Foo
352             return Foo(source)
353         builder = SCons.Builder.Builder(source_factory = FooFactory)
354         assert not builder.target_factory is FooFactory
355         assert builder.source_factory is FooFactory
356
357     def test_splitext(self):
358         """Test the splitext() method attached to a Builder."""
359         b = SCons.Builder.Builder()
360         assert b.splitext('foo') == ('foo','')
361         assert b.splitext('foo.bar') == ('foo','.bar')
362         assert b.splitext(os.path.join('foo.bar', 'blat')) == (os.path.join('foo.bar', 'blat'),'')
363
364         class MyBuilder(SCons.Builder.BuilderBase):
365             def splitext(self, path):
366                 return "called splitext()"
367
368         b = MyBuilder()
369         ret = b.splitext('xyz.c')
370         assert ret == "called splitext()", ret
371
372     def test_adjust_suffix(self):
373         """Test how a Builder adjusts file suffixes
374         """
375         b = SCons.Builder.Builder()
376         assert b.adjust_suffix('.foo') == '.foo'
377         assert b.adjust_suffix('foo') == '.foo'
378         assert b.adjust_suffix('$foo') == '$foo'
379
380         class MyBuilder(SCons.Builder.BuilderBase):
381             def adjust_suffix(self, suff):
382                 return "called adjust_suffix()"
383
384         b = MyBuilder()
385         ret = b.adjust_suffix('.foo')
386         assert ret == "called adjust_suffix()", ret
387
388     def test_prefix(self):
389         """Test Builder creation with a specified target prefix
390
391         Make sure that there is no '.' separator appended.
392         """
393         env = Environment()
394         builder = SCons.Builder.Builder(prefix = 'lib.')
395         assert builder.get_prefix(env) == 'lib.'
396         builder = SCons.Builder.Builder(prefix = 'lib')
397         assert builder.get_prefix(env) == 'lib'
398         tgt = builder(env, target = 'tgt1', source = 'src1')
399         assert tgt.path == 'libtgt1', \
400                 "Target has unexpected name: %s" % tgt.path
401         tgt = builder(env, target = 'tgt2a tgt2b', source = 'src2')
402         assert tgt.path == 'libtgt2a tgt2b', \
403                 "Target has unexpected name: %s" % tgt.path
404         tgt = builder(env, source = 'src3')
405         assert tgt.path == 'libsrc3', \
406                 "Target has unexpected name: %s" % tgt.path
407         tgt = builder(env, source = 'lib/src4')
408         assert tgt.path == os.path.join('lib', 'libsrc4'), \
409                 "Target has unexpected name: %s" % tgt.path
410         tgt = builder(env, target = 'lib/tgt5', source = 'lib/src5')
411         assert tgt.path == os.path.join('lib', 'libtgt5'), \
412                 "Target has unexpected name: %s" % tgt.path
413
414         def gen_prefix(env, sources):
415             return "gen_prefix() says " + env['FOO']
416         my_env = Environment(FOO = 'xyzzy')
417         builder = SCons.Builder.Builder(prefix = gen_prefix)
418         assert builder.get_prefix(my_env) == "gen_prefix() says xyzzy"
419         my_env['FOO'] = 'abracadabra'
420         assert builder.get_prefix(my_env) == "gen_prefix() says abracadabra"
421
422         def my_emit(env, sources):
423             return env.subst('$EMIT')
424         my_env = Environment(FOO = '.foo', EMIT = 'emit-')
425         builder = SCons.Builder.Builder(prefix = {None   : 'default-',
426                                                   '.in'  : 'out-',
427                                                   '.x'   : 'y-',
428                                                   '$FOO' : 'foo-',
429                                                   '.zzz' : my_emit})
430         tgt = builder(my_env, source = 'f1')
431         assert tgt.path == 'default-f1', tgt.path
432         tgt = builder(my_env, source = 'f2.c')
433         assert tgt.path == 'default-f2', tgt.path
434         tgt = builder(my_env, source = 'f3.in')
435         assert tgt.path == 'out-f3', tgt.path
436         tgt = builder(my_env, source = 'f4.x')
437         assert tgt.path == 'y-f4', tgt.path
438         tgt = builder(my_env, source = 'f5.foo')
439         assert tgt.path == 'foo-f5', tgt.path
440         tgt = builder(my_env, source = 'f6.zzz')
441         assert tgt.path == 'emit-f6', tgt.path
442
443     def test_src_suffix(self):
444         """Test Builder creation with a specified source file suffix
445         
446         Make sure that the '.' separator is appended to the
447         beginning if it isn't already present.
448         """
449         env = Environment(XSUFFIX = '.x', YSUFFIX = '.y')
450
451         b1 = SCons.Builder.Builder(src_suffix = '.c')
452         assert b1.src_suffixes(env) == ['.c'], b1.src_suffixes(env)
453
454         tgt = b1(env, target = 'tgt2', source = 'src2')
455         assert tgt.sources[0].path == 'src2.c', \
456                 "Source has unexpected name: %s" % tgt.sources[0].path
457
458         tgt = b1(env, target = 'tgt3', source = 'src3a src3b')
459         assert len(tgt.sources) == 1
460         assert tgt.sources[0].path == 'src3a src3b.c', \
461                 "Unexpected tgt.sources[0] name: %s" % tgt.sources[0].path
462
463         b2 = SCons.Builder.Builder(src_suffix = '.2', src_builder = b1)
464         assert b2.src_suffixes(env) == ['.2', '.c'], b2.src_suffixes(env)
465
466         b3 = SCons.Builder.Builder(action = {'.3a' : '', '.3b' : ''})
467         s = b3.src_suffixes(env)
468         s.sort()
469         assert s == ['.3a', '.3b'], s
470
471         b4 = SCons.Builder.Builder(src_suffix = '$XSUFFIX')
472         assert b4.src_suffixes(env) == ['.x'], b4.src_suffixes(env)
473
474         b5 = SCons.Builder.Builder(action = { '.y' : ''})
475         assert b5.src_suffixes(env) == ['.y'], b5.src_suffixes(env)
476
477     def test_suffix(self):
478         """Test Builder creation with a specified target suffix
479
480         Make sure that the '.' separator is appended to the
481         beginning if it isn't already present.
482         """
483         env = Environment()
484         builder = SCons.Builder.Builder(suffix = '.o')
485         assert builder.get_suffix(env) == '.o', builder.get_suffix(env)
486         builder = SCons.Builder.Builder(suffix = 'o')
487         assert builder.get_suffix(env) == '.o', builder.get_suffix(env)
488         tgt = builder(env, target = 'tgt3', source = 'src3')
489         assert tgt.path == 'tgt3.o', \
490                 "Target has unexpected name: %s" % tgt.path
491         tgt = builder(env, target = 'tgt4a tgt4b', source = 'src4')
492         assert tgt.path == 'tgt4a tgt4b.o', \
493                 "Target has unexpected name: %s" % tgt.path
494         tgt = builder(env, source = 'src5')
495         assert tgt.path == 'src5.o', \
496                 "Target has unexpected name: %s" % tgt.path
497
498         def gen_suffix(env, sources):
499             return "gen_suffix() says " + env['BAR']
500         my_env = Environment(BAR = 'hocus pocus')
501         builder = SCons.Builder.Builder(suffix = gen_suffix)
502         assert builder.get_suffix(my_env) == "gen_suffix() says hocus pocus", builder.get_suffix(my_env)
503         my_env['BAR'] = 'presto chango'
504         assert builder.get_suffix(my_env) == "gen_suffix() says presto chango"
505
506         def my_emit(env, sources):
507             return env.subst('$EMIT')
508         my_env = Environment(BAR = '.bar', EMIT = '.emit')
509         builder = SCons.Builder.Builder(suffix = {None   : '.default',
510                                                   '.in'  : '.out',
511                                                   '.x'   : '.y',
512                                                   '$BAR' : '.new',
513                                                   '.zzz' : my_emit})
514         tgt = builder(my_env, source = 'f1')
515         assert tgt.path == 'f1.default', tgt.path
516         tgt = builder(my_env, source = 'f2.c')
517         assert tgt.path == 'f2.default', tgt.path
518         tgt = builder(my_env, source = 'f3.in')
519         assert tgt.path == 'f3.out', tgt.path
520         tgt = builder(my_env, source = 'f4.x')
521         assert tgt.path == 'f4.y', tgt.path
522         tgt = builder(my_env, source = 'f5.bar')
523         assert tgt.path == 'f5.new', tgt.path
524         tgt = builder(my_env, source = 'f6.zzz')
525         assert tgt.path == 'f6.emit', tgt.path
526
527     def test_ListBuilder(self):
528         """Testing ListBuilder class."""
529         def function2(target, source, env, tlist = [outfile, outfile2], **kw):
530             for t in target:
531                 open(str(t), 'w').write("function2\n")
532             for t in tlist:
533                 if not t in map(str, target):
534                     open(t, 'w').write("function2\n")
535             return 1
536
537         env = Environment()
538         builder = SCons.Builder.Builder(action = function2)
539         tgts = builder(env, target = [outfile, outfile2], source = 'foo')
540         for t in tgts:
541             t.prepare()
542         try:
543             tgts[0].build()
544         except SCons.Errors.BuildError:
545             pass
546         c = test.read(outfile, 'r')
547         assert c == "function2\n", c
548         c = test.read(outfile2, 'r')
549         assert c == "function2\n", c
550
551         sub1_out = test.workpath('sub1', 'out')
552         sub2_out = test.workpath('sub2', 'out')
553
554         def function3(target, source, env, tlist = [sub1_out, sub2_out]):
555             for t in target:
556                 open(str(t), 'w').write("function3\n")
557             for t in tlist:
558                 if not t in map(str, target):
559                     open(t, 'w').write("function3\n")
560             return 1
561
562         builder = SCons.Builder.Builder(action = function3)
563         tgts = builder(env, target = [sub1_out, sub2_out], source = 'foo')
564         for t in tgts:
565             t.prepare()
566         try:
567             tgts[0].build()
568         except SCons.Errors.BuildError:
569             pass
570         c = test.read(sub1_out, 'r')
571         assert c == "function3\n", c
572         c = test.read(sub2_out, 'r')
573         assert c == "function3\n", c
574         assert os.path.exists(test.workpath('sub1'))
575         assert os.path.exists(test.workpath('sub2'))
576
577     def test_MultiStepBuilder(self):
578         """Testing MultiStepBuilder class."""
579         env = Environment()
580         builder1 = SCons.Builder.Builder(action='foo',
581                                          src_suffix='.bar',
582                                          suffix='.foo')
583         builder2 = SCons.Builder.MultiStepBuilder(action='bar',
584                                                   src_builder = builder1,
585                                                   src_suffix = '.foo')
586
587         tgt = builder2(env, target='baz', source=['test.bar', 'test2.foo', 'test3.txt'])
588         assert str(tgt.sources[0]) == 'test.foo', str(tgt.sources[0])
589         assert str(tgt.sources[0].sources[0]) == 'test.bar', \
590                str(tgt.sources[0].sources[0])
591         assert str(tgt.sources[1]) == 'test2.foo', str(tgt.sources[1])
592         assert str(tgt.sources[2]) == 'test3.txt', str(tgt.sources[2])
593
594         tgt = builder2(env, 'aaa.bar')
595         assert str(tgt) == 'aaa', str(tgt)
596         assert str(tgt.sources[0]) == 'aaa.foo', str(tgt.sources[0])
597         assert str(tgt.sources[0].sources[0]) == 'aaa.bar', \
598                str(tgt.sources[0].sources[0])
599
600         builder3 = SCons.Builder.MultiStepBuilder(action = 'foo',
601                                                   src_builder = 'xyzzy',
602                                                   src_suffix = '.xyzzy')
603         assert builder3.get_src_builders(Environment()) == []
604
605         builder4 = SCons.Builder.Builder(action='bld4',
606                                          src_suffix='.i',
607                                          suffix='_wrap.c')
608         builder5 = SCons.Builder.MultiStepBuilder(action='bld5',
609                                                   src_builder=builder4,
610                                                   suffix='.obj',
611                                                   src_suffix='.c')
612         builder6 = SCons.Builder.MultiStepBuilder(action='bld6',
613                                                   src_builder=builder5,
614                                                   suffix='.exe',
615                                                   src_suffix='.obj')
616         tgt = builder6(env, 'test', 'test.i')
617         assert str(tgt) == 'test.exe', str(tgt)
618         assert str(tgt.sources[0]) == 'test_wrap.obj', str(tgt.sources[0])
619         assert str(tgt.sources[0].sources[0]) == 'test_wrap.c', \
620                str(tgt.sources[0].sources[0])
621         assert str(tgt.sources[0].sources[0].sources[0]) == 'test.i', \
622                str(tgt.sources[0].sources[0].sources[0])
623         
624     def test_CompositeBuilder(self):
625         """Testing CompositeBuilder class."""
626         def func_action(target, source, env):
627             return 0
628         
629         env = Environment(BAR_SUFFIX = '.BAR2', FOO_SUFFIX = '.FOO2')
630         builder = SCons.Builder.Builder(action={ '.foo' : func_action,
631                                                  '.bar' : func_action,
632                                                  '$BAR_SUFFIX' : func_action,
633                                                  '$FOO_SUFFIX' : func_action })
634         
635         assert isinstance(builder, SCons.Builder.CompositeBuilder)
636         assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
637         tgt = builder(env, target='test1', source='test1.foo')
638         assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
639         assert tgt.builder.action is builder.action
640         tgt = builder(env, target='test2', source='test1.bar')
641         assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
642         assert tgt.builder.action is builder.action
643         flag = 0
644         tgt = builder(env, target='test3', source=['test2.bar', 'test1.foo'])
645         try:
646             tgt.build()
647         except SCons.Errors.UserError, e:
648             flag = 1
649         assert flag, "UserError should be thrown when we build targets with files of different suffixes."
650         match = str(e) == "While building `['test3']' from `test1.foo': Cannot build multiple sources with different extensions: .bar, .foo"
651         assert match, e
652
653         tgt = builder(env, target='test4', source=['test4.BAR2'])
654         assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
655         try:
656             tgt.build()
657             flag = 1
658         except SCons.Errors.UserError, e:
659             print e
660             flag = 0
661         assert flag, "It should be possible to define actions in composite builders using variables."
662         env['FOO_SUFFIX'] = '.BAR2'
663         builder.add_action('$NEW_SUFFIX', func_action)
664         flag = 0
665         tgt = builder(env, target='test5', source=['test5.BAR2'])
666         try:
667             tgt.build()
668         except SCons.Errors.UserError:
669             flag = 1
670         assert flag, "UserError should be thrown when we build targets with ambigous suffixes."
671         del env.d['FOO_SUFFIX']
672         del env.d['BAR_SUFFIX']
673
674         foo_bld = SCons.Builder.Builder(action = 'a-foo',
675                                         src_suffix = '.ina',
676                                         suffix = '.foo')
677         assert isinstance(foo_bld, SCons.Builder.BuilderBase)
678         builder = SCons.Builder.Builder(action = { '.foo' : 'foo',
679                                                    '.bar' : 'bar' },
680                                         src_builder = foo_bld)
681         assert isinstance(builder, SCons.Builder.CompositeBuilder)
682         assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
683
684         tgt = builder(env, target='t1', source='t1a.ina t1b.ina')
685         assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
686
687         tgt = builder(env, target='t2', source='t2a.foo t2b.ina')
688         assert isinstance(tgt.builder, SCons.Builder.MultiStepBuilder), tgt.builder.__dict__
689
690         bar_bld = SCons.Builder.Builder(action = 'a-bar',
691                                         src_suffix = '.inb',
692                                         suffix = '.bar')
693         assert isinstance(bar_bld, SCons.Builder.BuilderBase)
694         builder = SCons.Builder.Builder(action = { '.foo' : 'foo'},
695                                         src_builder = [foo_bld, bar_bld])
696         assert isinstance(builder, SCons.Builder.CompositeBuilder)
697         assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
698
699         builder.add_action('.bar', 'bar')
700
701         tgt = builder(env, target='t3-foo', source='t3a.foo t3b.ina')
702         assert isinstance(tgt.builder, SCons.Builder.MultiStepBuilder)
703
704         tgt = builder(env, target='t3-bar', source='t3a.bar t3b.inb')
705         assert isinstance(tgt.builder, SCons.Builder.MultiStepBuilder)
706
707         flag = 0
708         tgt = builder(env, target='t5', source=['test5a.foo', 'test5b.inb'])
709         try:
710             tgt.build()
711         except SCons.Errors.UserError, e:
712             flag = 1
713         assert flag, "UserError should be thrown when we build targets with files of different suffixes."
714         match = str(e) == "While building `['t5']' from `test5b.bar': Cannot build multiple sources with different extensions: .foo, .bar"
715         assert match, e
716
717         flag = 0
718         tgt = builder(env, target='t6', source=['test6a.bar', 'test6b.ina'])
719         try:
720             tgt.build()
721         except SCons.Errors.UserError, e:
722             flag = 1
723         assert flag, "UserError should be thrown when we build targets with files of different suffixes."
724         match = str(e) == "While building `['t6']' from `test6b.foo': Cannot build multiple sources with different extensions: .bar, .foo"
725         assert match, e
726
727         flag = 0
728         tgt = builder(env, target='t4', source=['test4a.ina', 'test4b.inb'])
729         try:
730             tgt.build()
731         except SCons.Errors.UserError, e:
732             flag = 1
733         assert flag, "UserError should be thrown when we build targets with files of different suffixes."
734         match = str(e) == "While building `['t4']' from `test4b.bar': Cannot build multiple sources with different extensions: .foo, .bar"
735         assert match, e
736
737         flag = 0
738         tgt = builder(env, target='t7', source=['test7'])
739         try:
740             tgt.build()
741         except SCons.Errors.UserError, e:
742             flag = 1
743         assert flag, "UserError should be thrown when we build targets with files of different suffixes."
744         match = str(e) == "While building `['t7']': Cannot deduce file extension from source files: ['test7']"
745         assert match, e
746
747         flag = 0
748         tgt = builder(env, target='t8', source=['test8.unknown'])
749         try:
750             tgt.build()
751         except SCons.Errors.UserError, e:
752             flag = 1
753         assert flag, "UserError should be thrown when we build targets with files of different suffixes."
754         match = str(e) == "While building `['t8']': Don't know how to build a file with suffix `.unknown'."
755         assert match, e
756
757     def test_target_scanner(self):
758         """Testing ability to set target and source scanners through a builder."""
759         global instanced
760         class TestScanner:
761             pass
762         tscan = TestScanner()
763         sscan = TestScanner()
764         env = Environment()
765         builder = SCons.Builder.Builder(target_scanner=tscan,
766                                         source_scanner=sscan)
767         tgt = builder(env, target='foo2', source='bar')
768         assert tgt.target_scanner == tscan, tgt.target_scanner
769         assert tgt.source_scanner == sscan, tgt.source_scanner
770
771         builder1 = SCons.Builder.Builder(action='foo',
772                                          src_suffix='.bar',
773                                          suffix='.foo')
774         builder2 = SCons.Builder.Builder(action='foo',
775                                          src_builder = builder1,
776                                          target_scanner = tscan,
777                                          source_scanner = tscan)
778         tgt = builder2(env, target='baz2', source='test.bar test2.foo test3.txt')
779         assert tgt.target_scanner == tscan, tgt.target_scanner
780         assert tgt.source_scanner == tscan, tgt.source_scanner
781
782     def test_src_scanner(slf):
783         """Testing ability to set a source file scanner through a builder."""
784         class TestScanner:
785             def key(self, env):
786                  return 'TestScannerkey'
787             def instance(self, env):
788                  return self
789
790         scanner = TestScanner()
791         builder = SCons.Builder.Builder(action='action')
792
793         # With no scanner specified, source_scanner and
794         # backup_source_scanner are None.
795         env1 = Environment()
796         tgt = builder(env1, target='foo1.x', source='bar.y')
797         src = tgt.sources[0]
798         assert tgt.target_scanner != scanner, tgt.target_scanner
799         assert src.source_scanner is None, src.source_scanner
800         assert src.backup_source_scanner is None, src.backup_source_scanner
801
802         # Later use of the same source file with an environment that
803         # has a scanner must still set the scanner.
804         env2 = Environment()
805         env2.scanner = scanner
806         tgt = builder(env2, target='foo2.x', source='bar.y')
807         src = tgt.sources[0]
808         assert tgt.target_scanner != scanner, tgt.target_scanner
809         assert src.source_scanner is None, src.source_scanner
810         assert src.backup_source_scanner == scanner, src.backup_source_scanner
811
812     def test_Builder_Args(self):
813         """Testing passing extra args to a builder."""
814         def buildFunc(target, source, env, s=self):
815             s.foo=env['foo']
816             s.bar=env['bar']
817             assert env['CC'] == 'mycc'
818
819         env=Environment(CC='cc')
820
821         builder = SCons.Builder.Builder(action=buildFunc)
822         tgt = builder(env, target='foo', source='bar', foo=1, bar=2, CC='mycc')
823         tgt.build()
824         assert self.foo == 1, self.foo
825         assert self.bar == 2, self.bar
826
827     def test_emitter(self):
828         """Test emitter functions."""
829         def emit(target, source, env):
830             foo = env.get('foo', 0)
831             bar = env.get('bar', 0)
832             for t in target:
833                 assert isinstance(t, MyNode)
834                 assert t.has_builder()
835             for s in source:
836                 assert isinstance(s, MyNode)
837             if foo:
838                 target.append("bar%d"%foo)
839             if bar:
840                 source.append("baz")
841             return ( target, source )
842
843         env = Environment()
844         builder = SCons.Builder.Builder(action='foo',
845                                         emitter=emit,
846                                         target_factory=MyNode,
847                                         source_factory=MyNode)
848         tgt = builder(env, target='foo2', source='bar')
849         assert str(tgt) == 'foo2', str(tgt)
850         assert str(tgt.sources[0]) == 'bar', str(tgt.sources[0])
851
852         tgt = builder(env, target='foo3', source='bar', foo=1)
853         assert len(tgt) == 2, len(tgt)
854         assert 'foo3' in map(str, tgt), map(str, tgt)
855         assert 'bar1' in map(str, tgt), map(str, tgt)
856
857         tgt = builder(env, target='foo4', source='bar', bar=1)
858         assert str(tgt) == 'foo4', str(tgt)
859         assert len(tgt.sources) == 2, len(tgt.sources)
860         assert 'baz' in map(str, tgt.sources), map(str, tgt.sources)
861         assert 'bar' in map(str, tgt.sources), map(str, tgt.sources)
862
863         env2=Environment(FOO=emit)
864         builder2=SCons.Builder.Builder(action='foo',
865                                        emitter="$FOO",
866                                        target_factory=MyNode,
867                                        source_factory=MyNode)
868
869         tgt = builder2(env2, target='foo5', source='bar')
870         assert str(tgt) == 'foo5', str(tgt)
871         assert str(tgt.sources[0]) == 'bar', str(tgt.sources[0])
872
873         tgt = builder2(env2, target='foo6', source='bar', foo=2)
874         assert len(tgt) == 2, len(tgt)
875         assert 'foo6' in map(str, tgt), map(str, tgt)
876         assert 'bar2' in map(str, tgt), map(str, tgt)
877
878         tgt = builder2(env2, target='foo7', source='bar', bar=1)
879         assert str(tgt) == 'foo7', str(tgt)
880         assert len(tgt.sources) == 2, len(tgt.sources)
881         assert 'baz' in map(str, tgt.sources), map(str, tgt.sources)
882         assert 'bar' in map(str, tgt.sources), map(str, tgt.sources)
883
884         builder2a=SCons.Builder.Builder(action='foo',
885                                         emitter="$FOO",
886                                         target_factory=MyNode,
887                                         source_factory=MyNode)
888         assert builder2 == builder2a, repr(builder2.__dict__) + "\n" + repr(builder2a.__dict__)
889
890         # Test that, if an emitter sets a builder on the passed-in
891         # targets and passes back new targets, the new builder doesn't
892         # get overwritten.
893         new_builder = SCons.Builder.Builder(action='new')
894         node = MyNode('foo8')
895         new_node = MyNode('foo8.new')
896         def emit3(target, source, env, nb=new_builder, nn=new_node):
897             for t in target:
898                 t.builder = nb
899             return [nn], source
900             
901         builder3=SCons.Builder.Builder(action='foo',
902                                        emitter=emit3,
903                                        target_factory=MyNode,
904                                        source_factory=MyNode)
905         tgt = builder3(env, target=node, source='bar')
906         assert tgt is new_node, tgt
907         assert tgt.builder is builder3, tgt.builder
908         assert node.builder is new_builder, node.builder
909
910         # Test use of a dictionary mapping file suffixes to
911         # emitter functions
912         def emit4a(target, source, env):
913             source = map(str, source)
914             target = map(lambda x: 'emit4a-' + x[:-3], source)
915             return (target, source)
916         def emit4b(target, source, env):
917             source = map(str, source)
918             target = map(lambda x: 'emit4b-' + x[:-3], source)
919             return (target, source)
920         builder4 = SCons.Builder.Builder(action='foo',
921                                          emitter={'.4a':emit4a,
922                                                   '.4b':emit4b},
923                                          target_factory=MyNode,
924                                          source_factory=MyNode)
925         tgt = builder4(env, source='aaa.4a')
926         assert str(tgt) == 'emit4a-aaa', str(tgt)
927         tgt = builder4(env, source='bbb.4b')
928         assert str(tgt) == 'emit4b-bbb', str(tgt)
929         tgt = builder4(env, source='ccc.4c')
930         assert str(tgt) == 'ccc', str(tgt)
931
932         def emit4c(target, source, env):
933             source = map(str, source)
934             target = map(lambda x: 'emit4c-' + x[:-3], source)
935             return (target, source)
936         builder4.add_emitter('.4c', emit4c)
937         tgt = builder4(env, source='ccc.4c')
938         assert str(tgt) == 'emit4c-ccc', str(tgt)
939
940         # Test a list of emitter functions.
941         def emit5a(target, source, env):
942             source = map(str, source)
943             target = target + map(lambda x: 'emit5a-' + x[:-2], source)
944             return (target, source)
945         def emit5b(target, source, env):
946             source = map(str, source)
947             target = target + map(lambda x: 'emit5b-' + x[:-2], source)
948             return (target, source)
949         builder5 = SCons.Builder.Builder(action='foo',
950                                          emitter=[emit5a, emit5b],
951                                          node_factory=MyNode)
952
953         tgts = builder5(env, target='target-5', source='aaa.5')
954         tgts = map(str, tgts)
955         assert tgts == ['target-5', 'emit5a-aaa', 'emit5b-aaa'], tgts
956
957         # Test a list of emitter functions through the environment.
958         def emit6a(target, source, env):
959             source = map(str, source)
960             target = target + map(lambda x: 'emit6a-' + x[:-2], source)
961             return (target, source)
962         def emit6b(target, source, env):
963             source = map(str, source)
964             target = target + map(lambda x: 'emit6b-' + x[:-2], source)
965             return (target, source)
966         builder6 = SCons.Builder.Builder(action='foo',
967                                          emitter='$EMITTERLIST',
968                                          node_factory=MyNode)
969                                          
970         env = Environment(EMITTERLIST = [emit6a, emit6b])
971
972         tgts = builder6(env, target='target-6', source='aaa.6')
973         tgts = map(str, tgts)
974         assert tgts == ['target-6', 'emit6a-aaa', 'emit6b-aaa'], tgts
975
976     def test_no_target(self):
977         """Test deducing the target from the source."""
978
979         env = Environment()
980         b = SCons.Builder.Builder(action='foo', suffix='.o')
981
982         tgt = b(env, 'aaa')
983         assert str(tgt) == 'aaa.o', str(tgt)
984         assert len(tgt.sources) == 1, map(str, tgt.sources)
985         assert str(tgt.sources[0]) == 'aaa', map(str, tgt.sources)
986
987         tgt = b(env, 'bbb.c')
988         assert str(tgt) == 'bbb.o', str(tgt)
989         assert len(tgt.sources) == 1, map(str, tgt.sources)
990         assert str(tgt.sources[0]) == 'bbb.c', map(str, tgt.sources)
991
992         tgt = b(env, 'ccc.x.c')
993         assert str(tgt) == 'ccc.x.o', str(tgt)
994         assert len(tgt.sources) == 1, map(str, tgt.sources)
995         assert str(tgt.sources[0]) == 'ccc.x.c', map(str, tgt.sources)
996
997         tgt = b(env, ['d0.c', 'd1.c'])
998         assert str(tgt) == 'd0.o', str(tgt)
999         assert len(tgt.sources) == 2,  map(str, tgt.sources)
1000         assert str(tgt.sources[0]) == 'd0.c', map(str, tgt.sources)
1001         assert str(tgt.sources[1]) == 'd1.c', map(str, tgt.sources)
1002
1003         tgt = b(env, source='eee')
1004         assert str(tgt) == 'eee.o', str(tgt)
1005         assert len(tgt.sources) == 1, map(str, tgt.sources)
1006         assert str(tgt.sources[0]) == 'eee', map(str, tgt.sources)
1007
1008         tgt = b(env, source='fff.c')
1009         assert str(tgt) == 'fff.o', str(tgt)
1010         assert len(tgt.sources) == 1, map(str, tgt.sources)
1011         assert str(tgt.sources[0]) == 'fff.c', map(str, tgt.sources)
1012
1013         tgt = b(env, source='ggg.x.c')
1014         assert str(tgt) == 'ggg.x.o', str(tgt)
1015         assert len(tgt.sources) == 1, map(str, tgt.sources)
1016         assert str(tgt.sources[0]) == 'ggg.x.c', map(str, tgt.sources)
1017
1018         tgt = b(env, source=['h0.c', 'h1.c'])
1019         assert str(tgt) == 'h0.o', str(tgt)
1020         assert len(tgt.sources) == 2,  map(str, tgt.sources)
1021         assert str(tgt.sources[0]) == 'h0.c', map(str, tgt.sources)
1022         assert str(tgt.sources[1]) == 'h1.c', map(str, tgt.sources)
1023
1024         w = b(env, target='i0.w', source=['i0.x'])
1025         y = b(env, target='i1.y', source=['i1.z'])
1026         tgt = b(env, source=[w, y])
1027         assert str(tgt) == 'i0.o', str(tgt)
1028         assert len(tgt.sources) == 2, map(str, tgt.sources)
1029         assert str(tgt.sources[0]) == 'i0.w', map(str, tgt.sources)
1030         assert str(tgt.sources[1]) == 'i1.y', map(str, tgt.sources)
1031
1032
1033 if __name__ == "__main__":
1034     suite = unittest.makeSuite(BuilderTestCase, 'test_')
1035     if not unittest.TextTestRunner().run(suite).wasSuccessful():
1036         sys.exit(1)