Allow libraries to specified as source files on the command line.
[scons.git] / src / engine / SCons / BuilderTests.py
1 #
2 # Copyright (c) 2001, 2002 Steven Knight
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 unittest
36
37 import TestCmd
38 import SCons.Builder
39 import SCons.Errors
40
41 # Initial setup of the common environment for all tests,
42 # a temporary working directory containing a
43 # script for writing arguments to an output file.
44 #
45 # We don't do this as a setUp() method because it's
46 # unnecessary to create a separate directory and script
47 # for each test, they can just use the one.
48 test = TestCmd.TestCmd(workdir = '')
49
50 test.write('act.py', """import os, string, sys
51 f = open(sys.argv[1], 'w')
52 f.write("act.py: '" + string.join(sys.argv[2:], "' '") + "'\\n")
53 try:
54     if sys.argv[3]:
55         f.write("act.py: '" + os.environ[sys.argv[3]] + "'\\n")
56 except:
57     pass
58 f.close()
59 sys.exit(0)
60 """)
61
62 act_py = test.workpath('act.py')
63 outfile = test.workpath('outfile')
64 outfile2 = test.workpath('outfile')
65
66 show_string = None
67 instanced = None
68 env_scanner = None
69 count = 0
70
71 class Environment:
72     def subst(self, s):
73         return s
74     def get_scanner(self, ext):
75         return env_scanner
76 env = Environment()
77
78 class BuilderTestCase(unittest.TestCase):
79
80     def test__call__(self):
81         """Test calling a builder to establish source dependencies
82         """
83         class Node:
84             def __init__(self, name):
85                 self.name = name
86                 self.sources = []
87                 self.builder = None
88             def __str__(self):
89                 return self.name
90             def builder_set(self, builder):
91                 self.builder = builder
92             def env_set(self, env, safe=0):
93                 self.env = env
94             def add_source(self, source):
95                 self.sources.extend(source)
96         builder = SCons.Builder.Builder(name="builder", action="foo", node_factory=Node)
97
98         n1 = Node("n1");
99         n2 = Node("n2");
100         builder(env, target = n1, source = n2)
101         assert n1.env == env
102         assert n1.builder == builder
103         assert n1.sources == [n2]
104         assert n2.env == env
105
106         target = builder(env, target = 'n3', source = 'n4')
107         assert target.name == 'n3'
108         assert target.sources[0].name == 'n4'
109
110         targets = builder(env, target = 'n4 n5', source = ['n6 n7'])
111         assert targets[0].name == 'n4'
112         assert targets[0].sources[0].name == 'n6 n7'
113         assert targets[1].name == 'n5'
114         assert targets[1].sources[0].name == 'n6 n7'
115
116         target = builder(env, target = ['n8 n9'], source = 'n10 n11')
117         assert target.name == 'n8 n9'
118         assert target.sources[0].name == 'n10'
119         assert target.sources[1].name == 'n11'
120
121     def test_noname(self):
122         """Test error reporting for missing name
123
124         Verify that the Builder constructor gives an error message if the
125         name is missing.
126         """
127         try:
128             b = SCons.Builder.Builder()
129         except SCons.Errors.UserError:
130             pass
131         else:
132             assert 0
133
134     def test_action(self):
135         """Test Builder creation
136
137         Verify that we can retrieve the supplied action attribute.
138         """
139         builder = SCons.Builder.Builder(name="builder", action="foo")
140         assert builder.action.command == "foo"
141
142     def test_cmp(self):
143         """Test simple comparisons of Builder objects
144         """
145         b1 = SCons.Builder.Builder(name="b1", src_suffix = '.o')
146         b2 = SCons.Builder.Builder(name="b1", src_suffix = '.o')
147         assert b1 == b2
148         b3 = SCons.Builder.Builder(name="b3", src_suffix = '.x')
149         assert b1 != b3
150         assert b2 != b3
151
152     def test_execute(self):
153         """Test execution of simple Builder objects
154         
155         One Builder is a string that executes an external command,
156         one is an internal Python function, one is a list
157         containing one of each.
158         """
159
160         def MyBuilder(**kw):
161             builder = apply(SCons.Builder.Builder, (), kw)
162             def no_show(str):
163                 pass
164             builder.action.show = no_show
165             return builder
166
167         python = sys.executable
168
169         cmd1 = r'%s %s %s xyzzy' % (python, act_py, outfile)
170
171         builder = MyBuilder(action = cmd1, name = "cmd1")
172         r = builder.execute()
173         assert r == 0
174         c = test.read(outfile, 'r')
175         assert c == "act.py: 'xyzzy'\n", c
176
177         cmd2 = r'%s %s %s $TARGET' % (python, act_py, outfile)
178
179         builder = MyBuilder(action = cmd2, name = "cmd2")
180         r = builder.execute(target = 'foo')
181         assert r == 0
182         c = test.read(outfile, 'r')
183         assert c == "act.py: 'foo'\n", c
184
185         cmd3 = r'%s %s %s ${TARGETS}' % (python, act_py, outfile)
186
187         builder = MyBuilder(action = cmd3, name = "cmd3")
188         r = builder.execute(target = ['aaa', 'bbb'])
189         assert r == 0
190         c = test.read(outfile, 'r')
191         assert c == "act.py: 'aaa' 'bbb'\n", c
192
193         cmd4 = r'%s %s %s $SOURCES' % (python, act_py, outfile)
194
195         builder = MyBuilder(action = cmd4, name = "cmd4")
196         r = builder.execute(source = ['one', 'two'])
197         assert r == 0
198         c = test.read(outfile, 'r')
199         assert c == "act.py: 'one' 'two'\n", c
200
201         cmd4 = r'%s %s %s ${SOURCES[:2]}' % (python, act_py, outfile)
202
203         builder = MyBuilder(action = cmd4, name = "cmd4")
204         r = builder.execute(source = ['three', 'four', 'five'])
205         assert r == 0
206         c = test.read(outfile, 'r')
207         assert c == "act.py: 'three' 'four'\n", c
208
209         cmd5 = r'%s %s %s $TARGET XYZZY' % (python, act_py, outfile)
210
211         builder = MyBuilder(action = cmd5, name = "cmd5")
212         r = builder.execute(target = 'out5', env = {'ENV' : {'XYZZY' : 'xyzzy'}})
213         assert r == 0
214         c = test.read(outfile, 'r')
215         assert c == "act.py: 'out5' 'XYZZY'\nact.py: 'xyzzy'\n", c
216
217         class Obj:
218             def __init__(self, str):
219                 self._str = str
220             def __str__(self):
221                 return self._str
222
223         cmd6 = r'%s %s %s ${TARGETS[1]} $TARGET ${SOURCES[:2]}' % (python, act_py, outfile)
224
225         builder = MyBuilder(action = cmd6, name = "cmd6")
226         r = builder.execute(target = [Obj('111'), Obj('222')],
227                             source = [Obj('333'), Obj('444'), Obj('555')])
228         assert r == 0
229         c = test.read(outfile, 'r')
230         assert c == "act.py: '222' '111' '333' '444'\n", c
231
232         cmd7 = '%s %s %s one\n\n%s %s %s two' % (python, act_py, outfile,
233                                                  python, act_py, outfile)
234         expect7 = '%s %s %s one\n%s %s %s two\n' % (python, act_py, outfile,
235                                                     python, act_py, outfile)
236
237         builder = MyBuilder(action = cmd7, name = "cmd7")
238
239         global show_string
240         show_string = ""
241         def my_show(string):
242             global show_string
243             show_string = show_string + string + "\n"
244         builder.action.show = my_show
245
246         r = builder.execute()
247         assert r == 0
248         assert show_string == expect7, show_string
249
250         global count
251         count = 0
252         def function1(**kw):
253             global count
254             count = count + 1
255             if not type(kw['target']) is type([]):
256                 kw['target'] = [ kw['target'] ]
257             for t in kw['target']:
258                 open(t, 'w').write("function1\n")
259             return 1
260
261         builder = MyBuilder(action = function1, name = "function1")
262         r = builder.execute(target = [outfile, outfile2])
263         assert r == 1
264         assert count == 1
265         c = test.read(outfile, 'r')
266         assert c == "function1\n", c
267         c = test.read(outfile2, 'r')
268         assert c == "function1\n", c
269
270         class class1a:
271             def __init__(self, **kw):
272                 open(kw['out'], 'w').write("class1a\n")
273
274         builder = MyBuilder(action = class1a, name = "class1a")
275         r = builder.execute(out = outfile)
276         assert r.__class__ == class1a
277         c = test.read(outfile, 'r')
278         assert c == "class1a\n", c
279
280         class class1b:
281             def __call__(self, **kw):
282                 open(kw['out'], 'w').write("class1b\n")
283                 return 2
284
285         builder = MyBuilder(action = class1b(), name = "class1b")
286         r = builder.execute(out = outfile)
287         assert r == 2
288         c = test.read(outfile, 'r')
289         assert c == "class1b\n", c
290
291         cmd2 = r'%s %s %s syzygy' % (python, act_py, outfile)
292
293         def function2(**kw):
294             open(kw['out'], 'a').write("function2\n")
295             return 0
296
297         class class2a:
298             def __call__(self, **kw):
299                 open(kw['out'], 'a').write("class2a\n")
300                 return 0
301
302         class class2b:
303             def __init__(self, **kw):
304                 open(kw['out'], 'a').write("class2b\n")
305
306         builder = MyBuilder(action = [cmd2, function2, class2a(), class2b], name = "clist")
307         r = builder.execute(out = outfile)
308         assert r.__class__ == class2b
309         c = test.read(outfile, 'r')
310         assert c == "act.py: 'syzygy'\nfunction2\nclass2a\nclass2b\n", c
311
312         if os.name == 'nt':
313             # NT treats execs of directories and non-executable files
314             # as "file not found" errors
315             expect_nonexistent = 1
316             expect_nonexecutable = 1
317         else:
318             expect_nonexistent = 127
319             expect_nonexecutable = 126
320
321         # Test that a nonexistent command returns 127
322         builder = MyBuilder(action = python + "_XyZzY_", name="badcmd")
323         r = builder.execute(out = outfile)
324         assert r == expect_nonexistent, "r == %d" % r
325
326         # Test that trying to execute a directory returns 126
327         dir, tail = os.path.split(python)
328         builder = MyBuilder(action = dir, name = "dir")
329         r = builder.execute(out = outfile)
330         assert r == expect_nonexecutable, "r == %d" % r
331
332         # Test that trying to execute a non-executable file returns 126
333         builder = MyBuilder(action = outfile, name = "badfile")
334         r = builder.execute(out = outfile)
335         assert r == expect_nonexecutable, "r == %d" % r
336
337     def test_get_contents(self):
338         """Test returning the signature contents of a Builder
339         """
340
341         b1 = SCons.Builder.Builder(name = "b1", action = "foo")
342         contents = b1.get_contents()
343         assert contents == "foo", contents
344
345         b2 = SCons.Builder.Builder(name = "b2", action = Func)
346         contents = b2.get_contents()
347         assert contents == "\177\036\000\177\037\000d\000\000S", repr(contents)
348
349         b3 = SCons.Builder.Builder(name = "b3", action = ["foo", Func, "bar"])
350         contents = b3.get_contents()
351         assert contents == "foo\177\036\000\177\037\000d\000\000Sbar", repr(contents)
352
353         b4 = SCons.Builder.Builder(name = "b4", action = "$_LIBFLAGS $_LIBDIRFLAGS $_INCFLAGS")
354         kw = {'LIBS'          : ['l1', 'l2'],
355               'LIBLINKPREFIX' : '-l',
356               'LIBLINKSUFFIX' : '',
357               'LIBPATH'       : ['lib'],
358               'LIBDIRPREFIX'  : '-L',
359               'LIBDIRSUFFIX'  : 'X',
360               'CPPPATH'       : ['c', 'p'],
361               'INCPREFIX'     : '-I',
362               'INCSUFFIX'     : ''}
363
364         contents = apply(b4.get_raw_contents, (), kw)
365         assert contents == "-ll1 -ll2 $( -LlibX $) $( -Ic -Ip $)", contents
366
367         contents = apply(b4.get_contents, (), kw)
368         assert contents == "-ll1 -ll2", "'%s'" % contents
369
370         # SCons.Node.FS has been imported by our import of
371         # SCons.Node.Builder.  It's kind of bogus that we don't
372         # import this ourselves before using it this way, but it's
373         # maybe a little cleaner than tying these tests directly
374         # to the other module via a direct import.
375         kw['dir'] = SCons.Node.FS.default_fs.Dir('d')
376
377         contents = apply(b4.get_raw_contents, (), kw)
378         expect = os.path.normpath("-ll1 -ll2 $( -Ld/libX $) $( -Id/c -Id/p $)")
379         assert contents == expect, contents + " != " + expect
380
381         contents = apply(b4.get_contents, (), kw)
382         expect = os.path.normpath("-ll1 -ll2")
383         assert contents == expect, contents + " != " + expect
384
385     def test_node_factory(self):
386         """Test a Builder that creates nodes of a specified class
387         """
388         class Foo:
389             pass
390         def FooFactory(target):
391             global Foo
392             return Foo(target)
393         builder = SCons.Builder.Builder(name = "builder", node_factory = FooFactory)
394         assert builder.node_factory is FooFactory
395
396     def test_prefix(self):
397         """Test Builder creation with a specified target prefix
398
399         Make sure that there is no '.' separator appended.
400         """
401         builder = SCons.Builder.Builder(name = "builder", prefix = 'lib.')
402         assert builder.prefix == 'lib.'
403         builder = SCons.Builder.Builder(name = "builder", prefix = 'lib')
404         assert builder.prefix == 'lib'
405         tgt = builder(env, target = 'tgt1', source = 'src1')
406         assert tgt.path == 'libtgt1', \
407                 "Target has unexpected name: %s" % tgt.path
408         tgts = builder(env, target = 'tgt2a tgt2b', source = 'src2')
409         assert tgts[0].path == 'libtgt2a', \
410                 "Target has unexpected name: %s" % tgts[0].path
411         assert tgts[1].path == 'libtgt2b', \
412                 "Target has unexpected name: %s" % tgts[1].path
413
414     def test_src_suffix(self):
415         """Test Builder creation with a specified source file suffix
416         
417         Make sure that the '.' separator is appended to the
418         beginning if it isn't already present.
419         """
420         builder = SCons.Builder.Builder(name = "builder", src_suffix = '.c')
421         assert builder.src_suffixes() == ['.c'], builder.src_suffixes()
422
423         tgt = builder(env, target = 'tgt2', source = 'src2')
424         assert tgt.sources[0].path == 'src2.c', \
425                 "Source has unexpected name: %s" % tgt.sources[0].path
426
427         tgt = builder(env, target = 'tgt3', source = 'src3a src3b')
428         assert tgt.sources[0].path == 'src3a.c', \
429                 "Sources[0] has unexpected name: %s" % tgt.sources[0].path
430         assert tgt.sources[1].path == 'src3b.c', \
431                 "Sources[1] has unexpected name: %s" % tgt.sources[1].path
432
433         b2 = SCons.Builder.Builder(name = "b2", src_suffix = '.2', src_builder = builder)
434         assert b2.src_suffixes() == ['.2', '.c'], b2.src_suffixes()
435
436         b3 = SCons.Builder.Builder(name = "b2", action = {'.3a' : '', '.3b' : ''})
437         s = b3.src_suffixes()
438         s.sort()
439         assert s == ['.3a', '.3b'], s
440
441     def test_suffix(self):
442         """Test Builder creation with a specified target suffix
443
444         Make sure that the '.' separator is appended to the
445         beginning if it isn't already present.
446         """
447         builder = SCons.Builder.Builder(name = "builder", suffix = '.o')
448         assert builder.suffix == '.o'
449         builder = SCons.Builder.Builder(name = "builder", suffix = 'o')
450         assert builder.suffix == '.o'
451         tgt = builder(env, target = 'tgt3', source = 'src3')
452         assert tgt.path == 'tgt3.o', \
453                 "Target has unexpected name: %s" % tgt[0].path
454         tgts = builder(env, target = 'tgt4a tgt4b', source = 'src4')
455         assert tgts[0].path == 'tgt4a.o', \
456                 "Target has unexpected name: %s" % tgts[0].path
457         tgts = builder(env, target = 'tgt4a tgt4b', source = 'src4')
458         assert tgts[1].path == 'tgt4b.o', \
459                 "Target has unexpected name: %s" % tgts[1].path
460
461     def test_ListBuilder(self):
462         """Testing ListBuilder class."""
463         global count
464         count = 0
465         def function2(**kw):
466             global count
467             count = count + 1
468             if not type(kw['target']) is type([]):
469                 kw['target'] = [ kw['target'] ]
470             for t in kw['target']:
471                 open(t, 'w').write("function2\n")
472             return 1
473
474         builder = SCons.Builder.Builder(action = function2, name = "function2")
475         tgts = builder(env, target = [outfile, outfile2], source = 'foo')
476         r = tgts[0].builder.execute(target = tgts[0])
477         assert r == 1, r
478         c = test.read(outfile, 'r')
479         assert c == "function2\n", c
480         c = test.read(outfile2, 'r')
481         assert c == "function2\n", c
482         r = tgts[1].builder.execute(target = tgts[1])
483         assert r == 1, r
484         assert count == 1, count
485
486     def test_MultiStepBuilder(self):
487         """Testing MultiStepBuilder class."""
488         builder1 = SCons.Builder.Builder(name = "builder1",
489                                          action='foo',
490                                          src_suffix='.bar',
491                                          suffix='.foo')
492         builder2 = SCons.Builder.MultiStepBuilder(name = "builder2",
493                                                   action='bar',
494                                                   src_builder = builder1,
495                                                   src_suffix = '.foo')
496         tgt = builder2(env, target='baz', source='test.bar test2.foo test3.txt')
497         assert str(tgt.sources[0]) == 'test.foo', str(tgt.sources[0])
498         assert str(tgt.sources[0].sources[0]) == 'test.bar', \
499                str(tgt.sources[0].sources[0])
500         assert str(tgt.sources[1]) == 'test2.foo', str(tgt.sources[1])
501         assert str(tgt.sources[2]) == 'test3.txt', str(tgt.sources[2])
502         
503     def test_CompositeBuilder(self):
504         """Testing CompositeBuilder class."""
505         builder = SCons.Builder.Builder(name = "builder",
506                                         action={ '.foo' : 'foo',
507                                                  '.bar' : 'bar' })
508         
509         assert isinstance(builder, SCons.Builder.CompositeBuilder)
510         tgt = builder(env, target='test1', source='test1.foo')
511         assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
512         assert tgt.builder.action.command == 'foo'
513         tgt = builder(env, target='test2', source='test2.bar')
514         assert tgt.builder.action.command == 'bar'
515         flag = 0
516         try:
517             tgt = builder(env, target='test2', source='test2.bar test1.foo')
518         except SCons.Errors.UserError:
519             flag = 1
520         assert flag, "UserError should be thrown when we build targets with files of different suffixes."
521
522         foo_bld = SCons.Builder.Builder(name = "foo_bld",
523                                         action = 'a-foo',
524                                         src_suffix = '.ina',
525                                         suffix = '.foo')
526         assert isinstance(foo_bld, SCons.Builder.BuilderBase)
527         builder = SCons.Builder.Builder(name = "builder",
528                                         action = { '.foo' : 'foo',
529                                                    '.bar' : 'bar' },
530                                         src_builder = foo_bld)
531         assert isinstance(builder, SCons.Builder.CompositeBuilder)
532
533         tgt = builder(env, target='t1', source='t1a.ina t1b.ina')
534         assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
535
536         tgt = builder(env, target='t2', source='t2a.foo t2b.ina')
537         assert isinstance(tgt.builder, SCons.Builder.MultiStepBuilder), tgt.builder.__dict__
538
539         bar_bld = SCons.Builder.Builder(name = "bar_bld",
540                                         action = 'a-bar',
541                                         src_suffix = '.inb',
542                                         suffix = '.bar')
543         assert isinstance(bar_bld, SCons.Builder.BuilderBase)
544         builder = SCons.Builder.Builder(name = "builder",
545                                         action = { '.foo' : 'foo',
546                                                    '.bar' : 'bar' },
547                                         src_builder = [foo_bld, bar_bld])
548         assert isinstance(builder, SCons.Builder.CompositeBuilder)
549
550         tgt = builder(env, target='t3-foo', source='t3a.foo t3b.ina')
551         assert isinstance(tgt.builder, SCons.Builder.MultiStepBuilder)
552
553         tgt = builder(env, target='t3-bar', source='t3a.bar t3b.inb')
554         assert isinstance(tgt.builder, SCons.Builder.MultiStepBuilder)
555
556         flag = 0
557         try:
558             tgt = builder(env, target='t5', source='test5a.foo test5b.inb')
559         except SCons.Errors.UserError:
560             flag = 1
561         assert flag, "UserError should be thrown when we build targets with files of different suffixes."
562
563         flag = 0
564         try:
565             tgt = builder(env, target='t6', source='test6a.bar test6b.ina')
566         except SCons.Errors.UserError:
567             flag = 1
568         assert flag, "UserError should be thrown when we build targets with files of different suffixes."
569
570         flag = 0
571         try:
572             tgt = builder(env, target='t4', source='test4a.ina test4b.inb')
573         except SCons.Errors.UserError:
574             flag = 1
575         assert flag, "UserError should be thrown when we build targets with files of different suffixes."
576
577     def test_build_scanner(self):
578         """Testing ability to set a target scanner through a builder."""
579         global instanced
580         class TestScanner:
581             def instance(self, env):
582                 global instanced
583                 instanced = 1
584                 return self
585         scn = TestScanner()
586         builder=SCons.Builder.Builder(name = "builder", scanner=scn)
587         tgt = builder(env, target='foo', source='bar')
588         assert scn in tgt.scanners, tgt.scanners
589         assert instanced
590
591         instanced = None
592         builder1 = SCons.Builder.Builder(name = "builder1",
593                                          action='foo',
594                                          src_suffix='.bar',
595                                          suffix='.foo')
596         builder2 = SCons.Builder.Builder(name = "builder2",
597                                          action='foo',
598                                          src_builder = builder1,
599                                          scanner = scn)
600         tgt = builder2(env, target='baz', source='test.bar test2.foo test3.txt')
601         assert scn in tgt.scanners, tgt.scanners
602         assert instanced
603
604     def test_src_scanner(slf):
605         """Testing ability to set a source file scanner through a builder."""
606         global env_scanner
607         class TestScanner:
608             def key(self, env):
609                  return 'TestScannerkey'
610             def instance(self, env):
611                  return self
612         env_scanner = TestScanner()
613         builder = SCons.Builder.Builder(name = "builder", action='action')
614         tgt = builder(env, target='foo', source='bar')
615         assert not tgt.scanners == [ env_scanner ]
616         assert tgt.sources[0].scanners == [ env_scanner ]
617
618 if __name__ == "__main__":
619     suite = unittest.makeSuite(BuilderTestCase, 'test_')
620     if not unittest.TextTestRunner().run(suite).wasSuccessful():
621         sys.exit(1)