Followon to PR #4348: more bool fixes
[scons.git] / SCons / BuilderTests.py
blob379d18b0305ca47891c84344a4437e299504a1f9
1 # MIT License
3 # Copyright The SCons Foundation
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:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
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.
24 import SCons.compat
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() -> None:
31 pass
33 from collections import UserList
34 import io
35 import os.path
36 import re
37 import sys
38 import unittest
40 import TestCmd
42 import SCons.Action
43 import SCons.Builder
44 import SCons.Environment
45 import SCons.Errors
46 import SCons.Subst
47 import SCons.Util
49 sys.stdout = io.StringIO()
51 # Initial setup of the common environment for all tests,
52 # a temporary working directory containing a
53 # script for writing arguments to an output file.
55 # We don't do this as a setUp() method because it's
56 # unnecessary to create a separate directory and script
57 # for each test, they can just use the one.
58 test = TestCmd.TestCmd(workdir = '')
60 outfile = test.workpath('outfile')
61 outfile2 = test.workpath('outfile2')
63 infile = test.workpath('infile')
64 test.write(infile, "infile\n")
66 show_string = None
68 scons_env = SCons.Environment.Environment()
70 env_arg2nodes_called = None
72 class Environment:
73 def __init__(self, **kw) -> None:
74 self.d = {}
75 self.d['SHELL'] = scons_env['SHELL']
76 self.d['SPAWN'] = scons_env['SPAWN']
77 self.d['ESCAPE'] = scons_env['ESCAPE']
78 for k, v in kw.items():
79 self.d[k] = v
80 global env_arg2nodes_called
81 env_arg2nodes_called = None
82 self.scanner = None
83 self.fs = SCons.Node.FS.FS()
84 def subst(self, s):
85 if not SCons.Util.is_String(s):
86 return s
87 def substitute(m, d=self.d):
88 return d.get(m.group(1), '')
89 return re.sub(r'\$(\w+)', substitute, s)
90 def subst_target_source(self, string, raw: int=0, target=None,
91 source=None, dict=None, conv=None):
92 return SCons.Subst.scons_subst(string, self, raw, target,
93 source, dict, conv)
94 def subst_list(self, string, raw: int=0, target=None, source=None, conv=None):
95 return SCons.Subst.scons_subst_list(string, self, raw, target,
96 source, {}, {}, conv)
97 def arg2nodes(self, args, factory, **kw):
98 global env_arg2nodes_called
99 env_arg2nodes_called = 1
100 if not SCons.Util.is_List(args):
101 args = [args]
102 list = []
103 for a in args:
104 if SCons.Util.is_String(a):
105 a = factory(self.subst(a))
106 list.append(a)
107 return list
108 def get_factory(self, factory):
109 return factory or self.fs.File
110 def get_scanner(self, ext):
111 return self.scanner
112 def Dictionary(self):
113 return {}
114 def autogenerate(self, dir: str=''):
115 return {}
116 def __setitem__(self, item, var) -> None:
117 self.d[item] = var
118 def __getitem__(self, item):
119 return self.d[item]
120 def __contains__(self, key) -> bool:
121 return key in self.d
122 def keys(self):
123 return list(self.d.keys())
124 def get(self, key, value=None):
125 return self.d.get(key, value)
126 def Override(self, overrides):
127 env = Environment(**self.d)
128 env.d.update(overrides)
129 env.scanner = self.scanner
130 return env
131 def _update(self, dict) -> None:
132 self.d.update(dict)
133 def items(self):
134 return list(self.d.items())
135 def sig_dict(self):
136 d = {}
137 for k,v in self.items(): d[k] = v
138 d['TARGETS'] = ['__t1__', '__t2__', '__t3__', '__t4__', '__t5__', '__t6__']
139 d['TARGET'] = d['TARGETS'][0]
140 d['SOURCES'] = ['__s1__', '__s2__', '__s3__', '__s4__', '__s5__', '__s6__']
141 d['SOURCE'] = d['SOURCES'][0]
142 return d
143 def __eq__(self, other):
144 return self.scanner == other.scanner or self.d == other.d
146 class MyAction:
147 def __init__(self, action) -> None:
148 self.action = action
149 def __call__(self, *args, **kw) -> None:
150 pass
151 def get_executor(self, env, overrides, tlist, slist, executor_kw):
152 return ['executor'] + [self.action]
154 class MyNode_without_target_from_source:
155 def __init__(self, name) -> None:
156 self.name = name
157 self.sources = []
158 self.builder = None
159 self.is_explicit = None
160 self.side_effect = 0
161 def get_suffix(self):
162 return os.path.splitext(self.name)[1]
163 def disambiguate(self):
164 return self
165 def __str__(self) -> str:
166 return self.name
167 def builder_set(self, builder) -> None:
168 self.builder = builder
169 def has_builder(self) -> bool:
170 return self.builder is not None
171 def set_explicit(self, is_explicit) -> None:
172 self.is_explicit = is_explicit
173 def has_explicit_builder(self) -> bool:
174 return self.is_explicit
175 def env_set(self, env, safe: bool=False) -> None:
176 self.env = env
177 def add_source(self, source) -> None:
178 self.sources.extend(source)
179 def scanner_key(self):
180 return self.name
181 def is_derived(self) -> bool:
182 return self.has_builder()
183 def generate_build_env(self, env):
184 return env
185 def get_build_env(self):
186 return self.executor.get_build_env()
187 def set_executor(self, executor) -> None:
188 self.executor = executor
189 def get_executor(self, create: int=1):
190 return self.executor
192 class MyNode(MyNode_without_target_from_source):
193 def target_from_source(self, prefix, suffix, stripext):
194 return MyNode(prefix + stripext(str(self))[0] + suffix)
196 class BuilderTestCase(unittest.TestCase):
198 def test__init__(self) -> None:
199 """Test simple Builder creation
201 builder = SCons.Builder.Builder(action="foo")
202 assert builder is not None, builder
203 builder = SCons.Builder.Builder(action="foo", OVERRIDE='x')
204 x = builder.overrides['OVERRIDE']
205 assert x == 'x', x
207 def test__bool__(self) -> None:
208 """Test a builder raising an exception when __bool__ is called. """
210 # basic test: explicitly call it
211 builder = SCons.Builder.Builder(action="foo")
212 exc_caught = None
213 try:
214 builder.__bool__()
215 except SCons.Errors.InternalError:
216 exc_caught = 1
217 assert exc_caught, "did not catch expected InternalError exception"
219 class Node:
220 pass
222 # the interesting test: checking the attribute this way
223 # should call back to Builder.__bool__ - so we can tell
224 # people not to check that way (for performance).
225 # TODO: workaround #3860: with just a "pass" in the check body,
226 # py3.10a optimizes out the whole thing, so add a "real" stmt
227 n = Node()
228 n.builder = builder
229 exc_caught = None
230 try:
231 if n.builder:
232 #pass
233 _ = True
234 except SCons.Errors.InternalError:
235 exc_caught = 1
236 assert exc_caught, "did not catch expected InternalError exception"
238 def test__call__(self):
239 """Test calling a builder to establish source dependencies
241 env = Environment()
242 builder = SCons.Builder.Builder(action="foo",
243 target_factory=MyNode,
244 source_factory=MyNode)
246 tgt = builder(env, source=[])
247 assert tgt == [], tgt
249 n1 = MyNode("n1")
250 n2 = MyNode("n2")
251 builder(env, target = n1, source = n2)
252 assert env_arg2nodes_called
253 assert n1.env == env, n1.env
254 assert n1.builder == builder, n1.builder
255 assert n1.sources == [n2], n1.sources
256 assert n1.executor, "no executor found"
257 assert not hasattr(n2, 'env')
259 l = [1]
260 ul = UserList([2])
261 try:
262 l.extend(ul)
263 except TypeError:
264 def mystr(l):
265 return str(list(map(str, l)))
266 else:
267 mystr = str
269 nnn1 = MyNode("nnn1")
270 nnn2 = MyNode("nnn2")
271 tlist = builder(env, target = [nnn1, nnn2], source = [])
272 s = mystr(tlist)
273 assert s == "['nnn1', 'nnn2']", s
274 l = list(map(str, tlist))
275 assert l == ['nnn1', 'nnn2'], l
277 tlist = builder(env, target = 'n3', source = 'n4')
278 s = mystr(tlist)
279 assert s == "['n3']", s
280 target = tlist[0]
281 l = list(map(str, tlist))
282 assert l == ['n3'], l
283 assert target.name == 'n3'
284 assert target.sources[0].name == 'n4'
286 tlist = builder(env, target = 'n4 n5', source = ['n6 n7'])
287 s = mystr(tlist)
288 assert s == "['n4 n5']", s
289 l = list(map(str, tlist))
290 assert l == ['n4 n5'], l
291 target = tlist[0]
292 assert target.name == 'n4 n5'
293 assert target.sources[0].name == 'n6 n7'
295 tlist = builder(env, target = ['n8 n9'], source = 'n10 n11')
296 s = mystr(tlist)
297 assert s == "['n8 n9']", s
298 l = list(map(str, tlist))
299 assert l == ['n8 n9'], l
300 target = tlist[0]
301 assert target.name == 'n8 n9'
302 assert target.sources[0].name == 'n10 n11'
304 # A test to be uncommented when we freeze the environment
305 # as part of calling the builder.
306 #env1 = Environment(VAR='foo')
307 #target = builder(env1, target = 'n12', source = 'n13')
308 #env1['VAR'] = 'bar'
309 #be = target.get_build_env()
310 #assert be['VAR'] == 'foo', be['VAR']
312 n20 = MyNode_without_target_from_source('n20')
313 flag = 0
314 try:
315 target = builder(env, None, source=n20)
316 except SCons.Errors.UserError as e:
317 flag = 1
318 assert flag, "UserError should be thrown if a source node can't create a target."
320 builder = SCons.Builder.Builder(action="foo",
321 target_factory=MyNode,
322 source_factory=MyNode,
323 prefix='p-',
324 suffix='.s')
325 target = builder(env, None, source='n21')[0]
326 assert target.name == 'p-n21.s', target
328 builder = SCons.Builder.Builder(misspelled_action="foo",
329 suffix = '.s')
330 try:
331 builder(env, target = 'n22', source = 'n22')
332 except SCons.Errors.UserError as e:
333 pass
334 else:
335 raise Exception("Did not catch expected UserError.")
337 builder = SCons.Builder.Builder(action="foo")
338 target = builder(env, None, source='n22', srcdir='src_dir')[0]
339 p = target.sources[0].get_internal_path()
340 assert p == os.path.join('src_dir', 'n22'), p
342 def test_mistaken_variables(self) -> None:
343 """Test keyword arguments that are often mistakes
345 import SCons.Warnings
346 env = Environment()
347 builder = SCons.Builder.Builder(action="foo")
349 save_warn = SCons.Warnings.warn
350 warned = []
351 def my_warn(exception, warning, warned=warned) -> None:
352 warned.append(warning)
353 SCons.Warnings.warn = my_warn
355 try:
356 target = builder(env, 'mistaken1', sources='mistaken1.c')
357 assert warned == ["Did you mean to use `source' instead of `sources'?"], warned
358 del warned[:]
360 target = builder(env, 'mistaken2', targets='mistaken2.c')
361 assert warned == ["Did you mean to use `target' instead of `targets'?"], warned
362 del warned[:]
364 target = builder(env, 'mistaken3', targets='mistaken3', sources='mistaken3.c')
365 assert "Did you mean to use `source' instead of `sources'?" in warned, warned
366 assert "Did you mean to use `target' instead of `targets'?" in warned, warned
367 del warned[:]
368 finally:
369 SCons.Warnings.warn = save_warn
371 def test_action(self) -> None:
372 """Test Builder creation
374 Verify that we can retrieve the supplied action attribute.
376 builder = SCons.Builder.Builder(action="foo")
377 assert builder.action.cmd_list == "foo"
379 def func() -> None:
380 pass
381 builder = SCons.Builder.Builder(action=func)
382 assert isinstance(builder.action, SCons.Action.FunctionAction)
383 # Preserve the following so that the baseline test will fail.
384 # Remove it in favor of the previous test at some convenient
385 # point in the future.
386 assert builder.action.execfunction == func
388 def test_generator(self) -> None:
389 """Test Builder creation given a generator function."""
391 def generator() -> None:
392 pass
394 builder = SCons.Builder.Builder(generator=generator)
395 assert builder.action.generator == generator
397 def test_cmp(self) -> None:
398 """Test simple comparisons of Builder objects
400 b1 = SCons.Builder.Builder(src_suffix = '.o')
401 b2 = SCons.Builder.Builder(src_suffix = '.o')
402 assert b1 == b2
403 b3 = SCons.Builder.Builder(src_suffix = '.x')
404 assert b1 != b3
405 assert b2 != b3
407 def test_target_factory(self) -> None:
408 """Test a Builder that creates target nodes of a specified class
410 class Foo:
411 pass
412 def FooFactory(target):
413 global Foo
414 return Foo(target)
415 builder = SCons.Builder.Builder(target_factory = FooFactory)
416 assert builder.target_factory is FooFactory
417 assert builder.source_factory is not FooFactory
419 def test_source_factory(self) -> None:
420 """Test a Builder that creates source nodes of a specified class
422 class Foo:
423 pass
424 def FooFactory(source):
425 global Foo
426 return Foo(source)
427 builder = SCons.Builder.Builder(source_factory = FooFactory)
428 assert builder.target_factory is not FooFactory
429 assert builder.source_factory is FooFactory
431 def test_splitext(self) -> None:
432 """Test the splitext() method attached to a Builder."""
433 b = SCons.Builder.Builder()
434 assert b.splitext('foo') == ('foo','')
435 assert b.splitext('foo.bar') == ('foo','.bar')
436 assert b.splitext(os.path.join('foo.bar', 'blat')) == (os.path.join('foo.bar', 'blat'),'')
438 class MyBuilder(SCons.Builder.BuilderBase):
439 def splitext(self, path) -> str:
440 return "called splitext()"
442 b = MyBuilder()
443 ret = b.splitext('xyz.c')
444 assert ret == "called splitext()", ret
446 def test_adjust_suffix(self) -> None:
447 """Test how a Builder adjusts file suffixes
449 b = SCons.Builder.Builder()
450 assert b.adjust_suffix('.foo') == '.foo'
451 assert b.adjust_suffix('foo') == '.foo'
452 assert b.adjust_suffix('$foo') == '$foo'
454 class MyBuilder(SCons.Builder.BuilderBase):
455 def adjust_suffix(self, suff) -> str:
456 return "called adjust_suffix()"
458 b = MyBuilder()
459 ret = b.adjust_suffix('.foo')
460 assert ret == "called adjust_suffix()", ret
462 def test_prefix(self) -> None:
463 """Test Builder creation with a specified target prefix
465 Make sure that there is no '.' separator appended.
467 env = Environment()
468 builder = SCons.Builder.Builder(prefix = 'lib.')
469 assert builder.get_prefix(env) == 'lib.'
470 builder = SCons.Builder.Builder(prefix = 'lib', action='')
471 assert builder.get_prefix(env) == 'lib'
472 tgt = builder(env, target = 'tgt1', source = 'src1')[0]
473 assert tgt.get_internal_path() == 'libtgt1', \
474 "Target has unexpected name: %s" % tgt.get_internal_path()
475 tgt = builder(env, target = 'tgt2a tgt2b', source = 'src2')[0]
476 assert tgt.get_internal_path() == 'libtgt2a tgt2b', \
477 "Target has unexpected name: %s" % tgt.get_internal_path()
478 tgt = builder(env, target = None, source = 'src3')[0]
479 assert tgt.get_internal_path() == 'libsrc3', \
480 "Target has unexpected name: %s" % tgt.get_internal_path()
481 tgt = builder(env, target = None, source = 'lib/src4')[0]
482 assert tgt.get_internal_path() == os.path.join('lib', 'libsrc4'), \
483 "Target has unexpected name: %s" % tgt.get_internal_path()
484 tgt = builder(env, target = 'lib/tgt5', source = 'lib/src5')[0]
485 assert tgt.get_internal_path() == os.path.join('lib', 'libtgt5'), \
486 "Target has unexpected name: %s" % tgt.get_internal_path()
488 def gen_prefix(env, sources):
489 return "gen_prefix() says " + env['FOO']
490 my_env = Environment(FOO = 'xyzzy')
491 builder = SCons.Builder.Builder(prefix = gen_prefix)
492 assert builder.get_prefix(my_env) == "gen_prefix() says xyzzy"
493 my_env['FOO'] = 'abracadabra'
494 assert builder.get_prefix(my_env) == "gen_prefix() says abracadabra"
496 def my_emit(env, sources):
497 return env.subst('$EMIT')
498 my_env = Environment(FOO = '.foo', EMIT = 'emit-')
499 builder = SCons.Builder.Builder(prefix = {None : 'default-',
500 '.in' : 'out-',
501 '.x' : 'y-',
502 '$FOO' : 'foo-',
503 '.zzz' : my_emit},
504 action = '')
505 tgt = builder(my_env, target = None, source = 'f1')[0]
506 assert tgt.get_internal_path() == 'default-f1', tgt.get_internal_path()
507 tgt = builder(my_env, target = None, source = 'f2.c')[0]
508 assert tgt.get_internal_path() == 'default-f2', tgt.get_internal_path()
509 tgt = builder(my_env, target = None, source = 'f3.in')[0]
510 assert tgt.get_internal_path() == 'out-f3', tgt.get_internal_path()
511 tgt = builder(my_env, target = None, source = 'f4.x')[0]
512 assert tgt.get_internal_path() == 'y-f4', tgt.get_internal_path()
513 tgt = builder(my_env, target = None, source = 'f5.foo')[0]
514 assert tgt.get_internal_path() == 'foo-f5', tgt.get_internal_path()
515 tgt = builder(my_env, target = None, source = 'f6.zzz')[0]
516 assert tgt.get_internal_path() == 'emit-f6', tgt.get_internal_path()
518 def test_set_suffix(self) -> None:
519 """Test the set_suffix() method"""
520 b = SCons.Builder.Builder(action='')
521 env = Environment(XSUFFIX = '.x')
523 s = b.get_suffix(env)
524 assert s == '', s
526 b.set_suffix('.foo')
527 s = b.get_suffix(env)
528 assert s == '.foo', s
530 b.set_suffix('$XSUFFIX')
531 s = b.get_suffix(env)
532 assert s == '.x', s
534 def test_src_suffix(self) -> None:
535 """Test Builder creation with a specified source file suffix
537 Make sure that the '.' separator is appended to the
538 beginning if it isn't already present.
540 env = Environment(XSUFFIX = '.x', YSUFFIX = '.y')
542 b1 = SCons.Builder.Builder(src_suffix = '.c', action='')
543 assert b1.src_suffixes(env) == ['.c'], b1.src_suffixes(env)
545 tgt = b1(env, target = 'tgt2', source = 'src2')[0]
546 assert tgt.sources[0].get_internal_path() == 'src2.c', \
547 "Source has unexpected name: %s" % tgt.sources[0].get_internal_path()
549 tgt = b1(env, target = 'tgt3', source = 'src3a src3b')[0]
550 assert len(tgt.sources) == 1
551 assert tgt.sources[0].get_internal_path() == 'src3a src3b.c', \
552 "Unexpected tgt.sources[0] name: %s" % tgt.sources[0].get_internal_path()
554 b2 = SCons.Builder.Builder(src_suffix = '.2', src_builder = b1)
555 r = sorted(b2.src_suffixes(env))
556 assert r == ['.2', '.c'], r
558 b3 = SCons.Builder.Builder(action = {'.3a' : '', '.3b' : ''})
559 s = sorted(b3.src_suffixes(env))
560 assert s == ['.3a', '.3b'], s
562 b4 = SCons.Builder.Builder(src_suffix = '$XSUFFIX')
563 assert b4.src_suffixes(env) == ['.x'], b4.src_suffixes(env)
565 b5 = SCons.Builder.Builder(action = { '.y' : ''})
566 assert b5.src_suffixes(env) == ['.y'], b5.src_suffixes(env)
568 def test_srcsuffix_nonext(self) -> None:
569 """Test target generation from non-extension source suffixes"""
570 env = Environment()
571 b6 = SCons.Builder.Builder(action = '',
572 src_suffix='_src.a',
573 suffix='.b')
574 tgt = b6(env, target=None, source='foo_src.a')
575 assert str(tgt[0]) == 'foo.b', str(tgt[0])
577 b7 = SCons.Builder.Builder(action = '',
578 src_suffix='_source.a',
579 suffix='_obj.b')
580 b8 = SCons.Builder.Builder(action = '',
581 src_builder=b7,
582 suffix='.c')
583 tgt = b8(env, target=None, source='foo_source.a')
584 assert str(tgt[0]) == 'foo_obj.c', str(tgt[0])
585 src = env.fs.File('foo_source.a')
586 tgt = b8(env, target=None, source=src)
587 assert str(tgt[0]) == 'foo_obj.c', str(tgt[0])
589 b9 = SCons.Builder.Builder(action={'_src.a' : 'srcaction'},
590 suffix='.c')
591 b9.add_action('_altsrc.b', 'altaction')
592 tgt = b9(env, target=None, source='foo_altsrc.b')
593 assert str(tgt[0]) == 'foo.c', str(tgt[0])
595 def test_src_suffix_expansion(self) -> None:
596 """Test handling source suffixes when an expansion is involved"""
597 env = Environment(OBJSUFFIX = '.obj')
599 b1 = SCons.Builder.Builder(action = '',
600 src_suffix='.c',
601 suffix='.obj')
602 b2 = SCons.Builder.Builder(action = '',
603 src_builder=b1,
604 src_suffix='.obj',
605 suffix='.exe')
606 tgt = b2(env, target=None, source=['foo$OBJSUFFIX'])
607 s = list(map(str, tgt[0].sources))
608 assert s == ['foo.obj'], s
610 def test_suffix(self) -> None:
611 """Test Builder creation with a specified target suffix
613 Make sure that the '.' separator is appended to the
614 beginning if it isn't already present.
616 env = Environment()
617 builder = SCons.Builder.Builder(suffix = '.o')
618 assert builder.get_suffix(env) == '.o', builder.get_suffix(env)
619 builder = SCons.Builder.Builder(suffix = 'o', action='')
620 assert builder.get_suffix(env) == '.o', builder.get_suffix(env)
621 tgt = builder(env, target = 'tgt3', source = 'src3')[0]
622 assert tgt.get_internal_path() == 'tgt3.o', \
623 "Target has unexpected name: %s" % tgt.get_internal_path()
624 tgt = builder(env, target = 'tgt4a tgt4b', source = 'src4')[0]
625 assert tgt.get_internal_path() == 'tgt4a tgt4b.o', \
626 "Target has unexpected name: %s" % tgt.get_internal_path()
627 tgt = builder(env, target = None, source = 'src5')[0]
628 assert tgt.get_internal_path() == 'src5.o', \
629 "Target has unexpected name: %s" % tgt.get_internal_path()
631 def gen_suffix(env, sources):
632 return "gen_suffix() says " + env['BAR']
633 my_env = Environment(BAR = 'hocus pocus')
634 builder = SCons.Builder.Builder(suffix = gen_suffix)
635 assert builder.get_suffix(my_env) == "gen_suffix() says hocus pocus", builder.get_suffix(my_env)
636 my_env['BAR'] = 'presto chango'
637 assert builder.get_suffix(my_env) == "gen_suffix() says presto chango"
639 def my_emit(env, sources):
640 return env.subst('$EMIT')
641 my_env = Environment(BAR = '.bar', EMIT = '.emit')
642 builder = SCons.Builder.Builder(suffix = {None : '.default',
643 '.in' : '.out',
644 '.x' : '.y',
645 '$BAR' : '.new',
646 '.zzz' : my_emit},
647 action='')
648 tgt = builder(my_env, target = None, source = 'f1')[0]
649 assert tgt.get_internal_path() == 'f1.default', tgt.get_internal_path()
650 tgt = builder(my_env, target = None, source = 'f2.c')[0]
651 assert tgt.get_internal_path() == 'f2.default', tgt.get_internal_path()
652 tgt = builder(my_env, target = None, source = 'f3.in')[0]
653 assert tgt.get_internal_path() == 'f3.out', tgt.get_internal_path()
654 tgt = builder(my_env, target = None, source = 'f4.x')[0]
655 assert tgt.get_internal_path() == 'f4.y', tgt.get_internal_path()
656 tgt = builder(my_env, target = None, source = 'f5.bar')[0]
657 assert tgt.get_internal_path() == 'f5.new', tgt.get_internal_path()
658 tgt = builder(my_env, target = None, source = 'f6.zzz')[0]
659 assert tgt.get_internal_path() == 'f6.emit', tgt.get_internal_path()
661 def test_single_source(self) -> None:
662 """Test Builder with single_source flag set"""
663 def func(target, source, env) -> None:
664 """create the file"""
665 with open(str(target[0]), "w"):
666 pass
667 if len(source) == 1 and len(target) == 1:
668 env['CNT'][0] = env['CNT'][0] + 1
670 env = Environment()
671 infiles = []
672 outfiles = []
673 for i in range(10):
674 infiles.append(test.workpath('%d.in' % i))
675 outfiles.append(test.workpath('%d.out' % i))
676 test.write(infiles[-1], "\n")
677 builder = SCons.Builder.Builder(action=SCons.Action.Action(func,None),
678 single_source = 1, suffix='.out')
679 env['CNT'] = [0]
680 tgt = builder(env, target=outfiles[0], source=infiles[0])[0]
681 s = str(tgt)
682 t = os.path.normcase(test.workpath('0.out'))
683 assert os.path.normcase(s) == t, s
684 tgt.prepare()
685 tgt.build()
686 assert env['CNT'][0] == 1, env['CNT'][0]
687 tgt = builder(env, outfiles[1], infiles[1])[0]
688 s = str(tgt)
689 t = os.path.normcase(test.workpath('1.out'))
690 assert os.path.normcase(s) == t, s
691 tgt.prepare()
692 tgt.build()
693 assert env['CNT'][0] == 2
694 tgts = builder(env, None, infiles[2:4])
695 s = list(map(str, tgts))
696 expect = [test.workpath('2.out'), test.workpath('3.out')]
697 expect = list(map(os.path.normcase, expect))
698 assert list(map(os.path.normcase, s)) == expect, s
699 for t in tgts: t.prepare()
700 tgts[0].build()
701 tgts[1].build()
702 assert env['CNT'][0] == 4
703 try:
704 tgt = builder(env, outfiles[4], infiles[4:6])
705 except SCons.Errors.UserError:
706 pass
707 else:
708 assert 0
709 try:
710 # The builder may output more than one target per input file.
711 tgt = builder(env, outfiles[4:6], infiles[4:6])
712 except SCons.Errors.UserError:
713 pass
714 else:
715 assert 0
718 def test_lists(self) -> None:
719 """Testing handling lists of targets and source"""
720 def function2(target, source, env, tlist = [outfile, outfile2], **kw) -> int:
721 for t in target:
722 with open(str(t), 'w') as f:
723 f.write("function2\n")
724 for t in tlist:
725 if t not in list(map(str, target)):
726 with open(t, 'w') as f:
727 f.write("function2\n")
728 return 1
730 env = Environment()
731 builder = SCons.Builder.Builder(action = function2)
733 tgts = builder(env, source=[])
734 assert tgts == [], tgts
736 tgts = builder(env, target = [outfile, outfile2], source = infile)
737 for t in tgts:
738 t.prepare()
739 try:
740 tgts[0].build()
741 except SCons.Errors.BuildError:
742 pass
743 c = test.read(outfile, 'r')
744 assert c == "function2\n", c
745 c = test.read(outfile2, 'r')
746 assert c == "function2\n", c
748 sub1_out = test.workpath('sub1', 'out')
749 sub2_out = test.workpath('sub2', 'out')
751 def function3(target, source, env, tlist = [sub1_out, sub2_out]) -> int:
752 for t in target:
753 with open(str(t), 'w') as f:
754 f.write("function3\n")
755 for t in tlist:
756 if t not in list(map(str, target)):
757 with open(t, 'w') as f:
758 f.write("function3\n")
759 return 1
761 builder = SCons.Builder.Builder(action = function3)
762 tgts = builder(env, target = [sub1_out, sub2_out], source = infile)
763 for t in tgts:
764 t.prepare()
765 try:
766 tgts[0].build()
767 except SCons.Errors.BuildError:
768 pass
769 c = test.read(sub1_out, 'r')
770 assert c == "function3\n", c
771 c = test.read(sub2_out, 'r')
772 assert c == "function3\n", c
773 assert os.path.exists(test.workpath('sub1'))
774 assert os.path.exists(test.workpath('sub2'))
776 def test_src_builder(self) -> None:
777 """Testing Builders with src_builder"""
778 # These used to be MultiStepBuilder objects until we
779 # eliminated it as a separate class
780 env = Environment()
781 builder1 = SCons.Builder.Builder(action='foo',
782 src_suffix='.bar',
783 suffix='.foo')
784 builder2 = SCons.Builder.Builder(action=MyAction('act'),
785 src_builder = builder1,
786 src_suffix = '.foo')
788 tgt = builder2(env, source=[])
789 assert tgt == [], tgt
791 sources = ['test.bar', 'test2.foo', 'test3.txt', 'test4']
792 tgt = builder2(env, target='baz', source=sources)[0]
793 s = str(tgt)
794 assert s == 'baz', s
795 s = list(map(str, tgt.sources))
796 assert s == ['test.foo', 'test2.foo', 'test3.txt', 'test4.foo'], s
797 s = list(map(str, tgt.sources[0].sources))
798 assert s == ['test.bar'], s
800 tgt = builder2(env, None, 'aaa.bar')[0]
801 s = str(tgt)
802 assert s == 'aaa', s
803 s = list(map(str, tgt.sources))
804 assert s == ['aaa.foo'], s
805 s = list(map(str, tgt.sources[0].sources))
806 assert s == ['aaa.bar'], s
808 builder3 = SCons.Builder.Builder(action='bld3')
809 assert builder3.src_builder is not builder1.src_builder
811 builder4 = SCons.Builder.Builder(action='bld4',
812 src_suffix='.i',
813 suffix='_wrap.c')
814 builder5 = SCons.Builder.Builder(action=MyAction('act'),
815 src_builder=builder4,
816 suffix='.obj',
817 src_suffix='.c')
818 builder6 = SCons.Builder.Builder(action=MyAction('act'),
819 src_builder=builder5,
820 suffix='.exe',
821 src_suffix='.obj')
822 tgt = builder6(env, 'test', 'test.i')[0]
823 s = str(tgt)
824 assert s == 'test.exe', s
825 s = list(map(str, tgt.sources))
826 assert s == ['test_wrap.obj'], s
827 s = list(map(str, tgt.sources[0].sources))
828 assert s == ['test_wrap.c'], s
829 s = list(map(str, tgt.sources[0].sources[0].sources))
830 assert s == ['test.i'], s
832 def test_target_scanner(self) -> None:
833 """Testing ability to set target and source scanners through a builder."""
834 global instanced
835 class TestScanner:
836 pass
837 tscan = TestScanner()
838 sscan = TestScanner()
839 env = Environment()
840 builder = SCons.Builder.Builder(target_scanner=tscan,
841 source_scanner=sscan,
842 action='')
843 tgt = builder(env, target='foo2', source='bar')[0]
844 assert tgt.builder.target_scanner == tscan, tgt.builder.target_scanner
845 assert tgt.builder.source_scanner == sscan, tgt.builder.source_scanner
847 builder1 = SCons.Builder.Builder(action='foo',
848 src_suffix='.bar',
849 suffix='.foo')
850 builder2 = SCons.Builder.Builder(action='foo',
851 src_builder = builder1,
852 target_scanner = tscan,
853 source_scanner = tscan)
854 tgt = builder2(env, target='baz2', source='test.bar test2.foo test3.txt')[0]
855 assert tgt.builder.target_scanner == tscan, tgt.builder.target_scanner
856 assert tgt.builder.source_scanner == tscan, tgt.builder.source_scanner
858 def test_actual_scanner(self) -> None:
859 """Test usage of actual Scanner objects."""
861 import SCons.Scanner
863 def func(self) -> None:
864 pass
866 scanner = SCons.Scanner.ScannerBase(func, name='fooscan')
868 b1 = SCons.Builder.Builder(action='bld', target_scanner=scanner)
869 b2 = SCons.Builder.Builder(action='bld', target_scanner=scanner)
870 b3 = SCons.Builder.Builder(action='bld')
872 assert b1 == b2
873 assert b1 != b3
875 def test_src_scanner(self) -> None:
876 """Testing ability to set a source file scanner through a builder."""
877 class TestScanner:
878 def key(self, env) -> str:
879 return 'TestScannerkey'
880 def instance(self, env):
881 return self
882 def select(self, node):
883 return self
884 name = 'TestScanner'
885 def __str__(self) -> str:
886 return self.name
888 scanner = TestScanner()
889 builder = SCons.Builder.Builder(action='action')
891 # With no scanner specified, source_scanner and
892 # backup_source_scanner are None.
893 bar_y = MyNode('bar.y')
894 env1 = Environment()
895 tgt = builder(env1, target='foo1.x', source='bar.y')[0]
896 src = tgt.sources[0]
897 assert tgt.builder.target_scanner != scanner, tgt.builder.target_scanner
898 assert tgt.builder.source_scanner is None, tgt.builder.source_scanner
899 assert tgt.get_source_scanner(bar_y) is None, tgt.get_source_scanner(bar_y)
900 assert not src.has_builder(), src.has_builder()
901 s = src.get_source_scanner(bar_y)
902 assert isinstance(s, SCons.Util.Null), repr(s)
904 # An Environment that has suffix-specified SCANNERS should
905 # provide a source scanner to the target.
906 class EnvTestScanner:
907 def key(self, env) -> str:
908 return '.y'
909 def instance(self, env):
910 return self
911 name = 'EnvTestScanner'
912 def __str__(self) -> str:
913 return self.name
914 def select(self, node):
915 return self
916 def path(self, env, dir=None):
917 return ()
918 def __call__(self, node, env, path):
919 return []
920 env3 = Environment(SCANNERS = [EnvTestScanner()])
921 env3.scanner = EnvTestScanner() # test env's version of SCANNERS
922 tgt = builder(env3, target='foo2.x', source='bar.y')[0]
923 src = tgt.sources[0]
924 assert tgt.builder.target_scanner != scanner, tgt.builder.target_scanner
925 assert not tgt.builder.source_scanner, tgt.builder.source_scanner
926 assert tgt.get_source_scanner(bar_y), tgt.get_source_scanner(bar_y)
927 assert str(tgt.get_source_scanner(bar_y)) == 'EnvTestScanner', tgt.get_source_scanner(bar_y)
928 assert not src.has_builder(), src.has_builder()
929 s = src.get_source_scanner(bar_y)
930 assert isinstance(s, SCons.Util.Null), repr(s)
932 # Can't simply specify the scanner as a builder argument; it's
933 # global to all invocations of this builder.
934 tgt = builder(env3, target='foo3.x', source='bar.y', source_scanner = scanner)[0]
935 src = tgt.sources[0]
936 assert tgt.builder.target_scanner != scanner, tgt.builder.target_scanner
937 assert not tgt.builder.source_scanner, tgt.builder.source_scanner
938 assert tgt.get_source_scanner(bar_y), tgt.get_source_scanner(bar_y)
939 assert str(tgt.get_source_scanner(bar_y)) == 'EnvTestScanner', tgt.get_source_scanner(bar_y)
940 assert not src.has_builder(), src.has_builder()
941 s = src.get_source_scanner(bar_y)
942 assert isinstance(s, SCons.Util.Null), s
944 # Now use a builder that actually has scanners and ensure that
945 # the target is set accordingly (using the specified scanner
946 # instead of the Environment's scanner)
947 builder = SCons.Builder.Builder(action='action',
948 source_scanner=scanner,
949 target_scanner=scanner)
950 tgt = builder(env3, target='foo4.x', source='bar.y')[0]
951 src = tgt.sources[0]
952 assert tgt.builder.target_scanner == scanner, tgt.builder.target_scanner
953 assert tgt.builder.source_scanner, tgt.builder.source_scanner
954 assert tgt.builder.source_scanner == scanner, tgt.builder.source_scanner
955 assert str(tgt.builder.source_scanner) == 'TestScanner', str(tgt.builder.source_scanner)
956 assert tgt.get_source_scanner(bar_y), tgt.get_source_scanner(bar_y)
957 assert tgt.get_source_scanner(bar_y) == scanner, tgt.get_source_scanner(bar_y)
958 assert str(tgt.get_source_scanner(bar_y)) == 'TestScanner', tgt.get_source_scanner(bar_y)
959 assert not src.has_builder(), src.has_builder()
960 s = src.get_source_scanner(bar_y)
961 assert isinstance(s, SCons.Util.Null), s
965 def test_Builder_API(self) -> None:
966 """Test Builder interface.
968 Some of this is tested elsewhere in this file, but this is a
969 quick collection of common operations on builders with various
970 forms of component specifications."""
972 builder = SCons.Builder.Builder()
973 env = Environment(BUILDERS={'Bld':builder})
975 r = builder.get_name(env)
976 assert r == 'Bld', r
977 r = builder.get_prefix(env)
978 assert r == '', r
979 r = builder.get_suffix(env)
980 assert r == '', r
981 r = builder.get_src_suffix(env)
982 assert r == '', r
983 r = builder.src_suffixes(env)
984 assert r == [], r
986 # src_suffix can be a single string or a list of strings
987 # src_suffixes() caches its return value, so we use a new
988 # Builder each time we do any of these tests
990 bld = SCons.Builder.Builder()
991 env = Environment(BUILDERS={'Bld':bld})
993 bld.set_src_suffix('.foo')
994 r = bld.get_src_suffix(env)
995 assert r == '.foo', r
996 r = bld.src_suffixes(env)
997 assert r == ['.foo'], r
999 bld = SCons.Builder.Builder()
1000 env = Environment(BUILDERS={'Bld':bld})
1002 bld.set_src_suffix(['.foo', '.bar'])
1003 r = bld.get_src_suffix(env)
1004 assert r == '.foo', r
1005 r = bld.src_suffixes(env)
1006 assert r == ['.foo', '.bar'], r
1008 bld = SCons.Builder.Builder()
1009 env = Environment(BUILDERS={'Bld':bld})
1011 bld.set_src_suffix(['.bar', '.foo'])
1012 r = bld.get_src_suffix(env)
1013 assert r == '.bar', r
1014 r = sorted(bld.src_suffixes(env))
1015 assert r == ['.bar', '.foo'], r
1017 # adjust_suffix normalizes the suffix, adding a `.' if needed
1019 r = builder.adjust_suffix('.foo')
1020 assert r == '.foo', r
1021 r = builder.adjust_suffix('_foo')
1022 assert r == '_foo', r
1023 r = builder.adjust_suffix('$foo')
1024 assert r == '$foo', r
1025 r = builder.adjust_suffix('foo')
1026 assert r == '.foo', r
1027 r = builder.adjust_suffix('f._$oo')
1028 assert r == '.f._$oo', r
1030 # prefix and suffix can be one of:
1031 # 1. a string (adjusted and env variables substituted),
1032 # 2. a function (passed (env,sources), returns suffix string)
1033 # 3. a dict of src_suffix:suffix settings, key==None is
1034 # default suffix (special case of #2, so adjust_suffix
1035 # not applied)
1037 builder = SCons.Builder.Builder(prefix='lib', suffix='foo')
1039 env = Environment(BUILDERS={'Bld':builder})
1040 r = builder.get_name(env)
1041 assert r == 'Bld', r
1042 r = builder.get_prefix(env)
1043 assert r == 'lib', r
1044 r = builder.get_suffix(env)
1045 assert r == '.foo', r
1047 mkpref = lambda env,sources: 'Lib'
1048 mksuff = lambda env,sources: '.Foo'
1049 builder = SCons.Builder.Builder(prefix=mkpref, suffix=mksuff)
1051 env = Environment(BUILDERS={'Bld':builder})
1052 r = builder.get_name(env)
1053 assert r == 'Bld', r
1054 r = builder.get_prefix(env)
1055 assert r == 'Lib', r
1056 r = builder.get_suffix(env)
1057 assert r == '.Foo', r
1059 builder = SCons.Builder.Builder(prefix='$PREF', suffix='$SUFF')
1061 env = Environment(BUILDERS={'Bld':builder},PREF="LIB",SUFF=".FOO")
1062 r = builder.get_name(env)
1063 assert r == 'Bld', r
1064 r = builder.get_prefix(env)
1065 assert r == 'LIB', r
1066 r = builder.get_suffix(env)
1067 assert r == '.FOO', r
1069 builder = SCons.Builder.Builder(prefix={None:'A_',
1070 '.C':'E_'},
1071 suffix={None:'.B',
1072 '.C':'.D'})
1074 env = Environment(BUILDERS={'Bld':builder})
1075 r = builder.get_name(env)
1076 assert r == 'Bld', r
1077 r = builder.get_prefix(env)
1078 assert r == 'A_', r
1079 r = builder.get_suffix(env)
1080 assert r == '.B', r
1081 r = builder.get_prefix(env, [MyNode('X.C')])
1082 assert r == 'E_', r
1083 r = builder.get_suffix(env, [MyNode('X.C')])
1084 assert r == '.D', r
1086 builder = SCons.Builder.Builder(prefix='A_', suffix={}, action={})
1087 env = Environment(BUILDERS={'Bld':builder})
1089 r = builder.get_name(env)
1090 assert r == 'Bld', r
1091 r = builder.get_prefix(env)
1092 assert r == 'A_', r
1093 r = builder.get_suffix(env)
1094 assert r is None, r
1095 r = builder.get_src_suffix(env)
1096 assert r == '', r
1097 r = builder.src_suffixes(env)
1098 assert r == [], r
1100 # Builder actions can be a string, a list, or a dictionary
1101 # whose keys are the source suffix. The add_action()
1102 # specifies a new source suffix/action binding.
1104 builder = SCons.Builder.Builder(prefix='A_', suffix={}, action={})
1105 env = Environment(BUILDERS={'Bld':builder})
1106 builder.add_action('.src_sfx1', 'FOO')
1108 r = builder.get_name(env)
1109 assert r == 'Bld', r
1110 r = builder.get_prefix(env)
1111 assert r == 'A_', r
1112 r = builder.get_suffix(env)
1113 assert r is None, r
1114 r = builder.get_suffix(env, [MyNode('X.src_sfx1')])
1115 assert r is None, r
1116 r = builder.get_src_suffix(env)
1117 assert r == '.src_sfx1', r
1118 r = builder.src_suffixes(env)
1119 assert r == ['.src_sfx1'], r
1121 builder = SCons.Builder.Builder(prefix='A_', suffix={}, action={})
1122 env = Environment(BUILDERS={'Bld':builder})
1123 builder.add_action('.src_sfx1', 'FOO')
1124 builder.add_action('.src_sfx2', 'BAR')
1126 r = builder.get_name(env)
1127 assert r == 'Bld', r
1128 r = builder.get_prefix(env)
1129 assert r == 'A_', r
1130 r = builder.get_suffix(env)
1131 assert r is None, r
1132 r = builder.get_src_suffix(env)
1133 assert r == '.src_sfx1', r
1134 r = sorted(builder.src_suffixes(env))
1135 assert r == ['.src_sfx1', '.src_sfx2'], r
1138 def test_Builder_Args(self) -> None:
1139 """Testing passing extra args to a builder."""
1140 def buildFunc(target, source, env, s=self) -> None:
1141 s.foo=env['foo']
1142 s.bar=env['bar']
1143 assert env['CC'] == 'mycc'
1145 env=Environment(CC='cc')
1147 builder = SCons.Builder.Builder(action=buildFunc)
1148 tgt = builder(env, target='foo', source='bar', foo=1, bar=2, CC='mycc')[0]
1149 tgt.build()
1150 assert self.foo == 1, self.foo
1151 assert self.bar == 2, self.bar
1153 def test_emitter(self) -> None:
1154 """Test emitter functions."""
1155 def emit(target, source, env):
1156 foo = env.get('foo', 0)
1157 bar = env.get('bar', 0)
1158 for t in target:
1159 assert isinstance(t, MyNode)
1160 assert t.has_builder()
1161 for s in source:
1162 assert isinstance(s, MyNode)
1163 if foo:
1164 target.append("bar%d"%foo)
1165 if bar:
1166 source.append("baz")
1167 return ( target, source )
1169 env = Environment()
1170 builder = SCons.Builder.Builder(action='foo',
1171 emitter=emit,
1172 target_factory=MyNode,
1173 source_factory=MyNode)
1174 tgt = builder(env, target='foo2', source='bar')[0]
1175 assert str(tgt) == 'foo2', str(tgt)
1176 assert str(tgt.sources[0]) == 'bar', str(tgt.sources[0])
1178 tgt = builder(env, target='foo3', source='bar', foo=1)
1179 assert len(tgt) == 2, len(tgt)
1180 assert 'foo3' in list(map(str, tgt)), list(map(str, tgt))
1181 assert 'bar1' in list(map(str, tgt)), list(map(str, tgt))
1183 tgt = builder(env, target='foo4', source='bar', bar=1)[0]
1184 assert str(tgt) == 'foo4', str(tgt)
1185 assert len(tgt.sources) == 2, len(tgt.sources)
1186 assert 'baz' in list(map(str, tgt.sources)), list(map(str, tgt.sources))
1187 assert 'bar' in list(map(str, tgt.sources)), list(map(str, tgt.sources))
1189 env2=Environment(FOO=emit)
1190 builder2=SCons.Builder.Builder(action='foo',
1191 emitter="$FOO",
1192 target_factory=MyNode,
1193 source_factory=MyNode)
1195 builder2a=SCons.Builder.Builder(action='foo',
1196 emitter="$FOO",
1197 target_factory=MyNode,
1198 source_factory=MyNode)
1200 assert builder2 == builder2a, repr(builder2.__dict__) + "\n" + repr(builder2a.__dict__)
1202 tgt = builder2(env2, target='foo5', source='bar')[0]
1203 assert str(tgt) == 'foo5', str(tgt)
1204 assert str(tgt.sources[0]) == 'bar', str(tgt.sources[0])
1206 tgt = builder2(env2, target='foo6', source='bar', foo=2)
1207 assert len(tgt) == 2, len(tgt)
1208 assert 'foo6' in list(map(str, tgt)), list(map(str, tgt))
1209 assert 'bar2' in list(map(str, tgt)), list(map(str, tgt))
1211 tgt = builder2(env2, target='foo7', source='bar', bar=1)[0]
1212 assert str(tgt) == 'foo7', str(tgt)
1213 assert len(tgt.sources) == 2, len(tgt.sources)
1214 assert 'baz' in list(map(str, tgt.sources)), list(map(str, tgt.sources))
1215 assert 'bar' in list(map(str, tgt.sources)), list(map(str, tgt.sources))
1217 def test_emitter_preserve_builder(self) -> None:
1218 """Test an emitter not overwriting a newly-set builder"""
1219 env = Environment()
1221 new_builder = SCons.Builder.Builder(action='new')
1222 node = MyNode('foo8')
1223 new_node = MyNode('foo8.new')
1225 def emit(target, source, env, nb=new_builder, nn=new_node):
1226 for t in target:
1227 t.builder = nb
1228 return [nn], source
1230 builder=SCons.Builder.Builder(action='foo',
1231 emitter=emit,
1232 target_factory=MyNode,
1233 source_factory=MyNode)
1234 tgt = builder(env, target=node, source='bar')[0]
1235 assert tgt is new_node, tgt
1236 assert tgt.builder is builder, tgt.builder
1237 assert node.builder is new_builder, node.builder
1239 def test_emitter_suffix_map(self) -> None:
1240 """Test mapping file suffixes to emitter functions"""
1241 env = Environment()
1243 def emit4a(target, source, env):
1244 source = list(map(str, source))
1245 target = ['emit4a-' + x[:-3] for x in source]
1246 return (target, source)
1247 def emit4b(target, source, env):
1248 source = list(map(str, source))
1249 target = ['emit4b-' + x[:-3] for x in source]
1250 return (target, source)
1252 builder = SCons.Builder.Builder(action='foo',
1253 emitter={'.4a':emit4a,
1254 '.4b':emit4b},
1255 target_factory=MyNode,
1256 source_factory=MyNode)
1257 tgt = builder(env, None, source='aaa.4a')[0]
1258 assert str(tgt) == 'emit4a-aaa', str(tgt)
1259 tgt = builder(env, None, source='bbb.4b')[0]
1260 assert str(tgt) == 'emit4b-bbb', str(tgt)
1261 tgt = builder(env, None, source='ccc.4c')[0]
1262 assert str(tgt) == 'ccc', str(tgt)
1264 def emit4c(target, source, env):
1265 source = list(map(str, source))
1266 target = ['emit4c-' + x[:-3] for x in source]
1267 return (target, source)
1269 builder.add_emitter('.4c', emit4c)
1270 tgt = builder(env, None, source='ccc.4c')[0]
1271 assert str(tgt) == 'emit4c-ccc', str(tgt)
1273 def test_emitter_function_list(self) -> None:
1274 """Test lists of emitter functions"""
1275 env = Environment()
1277 def emit1a(target, source, env):
1278 source = list(map(str, source))
1279 target = target + ['emit1a-' + x[:-2] for x in source]
1280 return (target, source)
1281 def emit1b(target, source, env):
1282 source = list(map(str, source))
1283 target = target + ['emit1b-' + x[:-2] for x in source]
1284 return (target, source)
1285 builder1 = SCons.Builder.Builder(action='foo',
1286 emitter=[emit1a, emit1b],
1287 node_factory=MyNode)
1289 tgts = builder1(env, target='target-1', source='aaa.1')
1290 tgts = list(map(str, tgts))
1291 assert tgts == ['target-1', 'emit1a-aaa', 'emit1b-aaa'], tgts
1293 # Test a list of emitter functions through the environment.
1294 def emit2a(target, source, env):
1295 source = list(map(str, source))
1296 target = target + ['emit2a-' + x[:-2] for x in source]
1297 return (target, source)
1298 def emit2b(target, source, env):
1299 source = list(map(str, source))
1300 target = target + ['emit2b-' + x[:-2] for x in source]
1301 return (target, source)
1302 builder2 = SCons.Builder.Builder(action='foo',
1303 emitter='$EMITTERLIST',
1304 node_factory=MyNode)
1306 env = Environment(EMITTERLIST = [emit2a, emit2b])
1308 tgts = builder2(env, target='target-2', source='aaa.2')
1309 tgts = list(map(str, tgts))
1310 assert tgts == ['target-2', 'emit2a-aaa', 'emit2b-aaa'], tgts
1312 def test_emitter_TARGET_SOURCE(self) -> None:
1313 """Test use of $TARGET and $SOURCE in emitter results"""
1315 env = SCons.Environment.Environment()
1317 def emit(target, source, env):
1318 return (target + ['${SOURCE}.s1', '${TARGET}.t1'],
1319 source + ['${TARGET}.t2', '${SOURCE}.s2'])
1321 builder = SCons.Builder.Builder(action='foo',
1322 emitter = emit,
1323 node_factory = MyNode)
1325 targets = builder(env, target = 'TTT', source ='SSS')
1326 sources = targets[0].sources
1327 targets = list(map(str, targets))
1328 sources = list(map(str, sources))
1329 assert targets == ['TTT', 'SSS.s1', 'TTT.t1'], targets
1330 assert sources == ['SSS', 'TTT.t2', 'SSS.s2'], targets
1332 def test_no_target(self) -> None:
1333 """Test deducing the target from the source."""
1335 env = Environment()
1336 b = SCons.Builder.Builder(action='foo', suffix='.o')
1338 tgt = b(env, None, 'aaa')[0]
1339 assert str(tgt) == 'aaa.o', str(tgt)
1340 assert len(tgt.sources) == 1, list(map(str, tgt.sources))
1341 assert str(tgt.sources[0]) == 'aaa', list(map(str, tgt.sources))
1343 tgt = b(env, None, 'bbb.c')[0]
1344 assert str(tgt) == 'bbb.o', str(tgt)
1345 assert len(tgt.sources) == 1, list(map(str, tgt.sources))
1346 assert str(tgt.sources[0]) == 'bbb.c', list(map(str, tgt.sources))
1348 tgt = b(env, None, 'ccc.x.c')[0]
1349 assert str(tgt) == 'ccc.x.o', str(tgt)
1350 assert len(tgt.sources) == 1, list(map(str, tgt.sources))
1351 assert str(tgt.sources[0]) == 'ccc.x.c', list(map(str, tgt.sources))
1353 tgt = b(env, None, ['d0.c', 'd1.c'])[0]
1354 assert str(tgt) == 'd0.o', str(tgt)
1355 assert len(tgt.sources) == 2, list(map(str, tgt.sources))
1356 assert str(tgt.sources[0]) == 'd0.c', list(map(str, tgt.sources))
1357 assert str(tgt.sources[1]) == 'd1.c', list(map(str, tgt.sources))
1359 tgt = b(env, target = None, source='eee')[0]
1360 assert str(tgt) == 'eee.o', str(tgt)
1361 assert len(tgt.sources) == 1, list(map(str, tgt.sources))
1362 assert str(tgt.sources[0]) == 'eee', list(map(str, tgt.sources))
1364 tgt = b(env, target = None, source='fff.c')[0]
1365 assert str(tgt) == 'fff.o', str(tgt)
1366 assert len(tgt.sources) == 1, list(map(str, tgt.sources))
1367 assert str(tgt.sources[0]) == 'fff.c', list(map(str, tgt.sources))
1369 tgt = b(env, target = None, source='ggg.x.c')[0]
1370 assert str(tgt) == 'ggg.x.o', str(tgt)
1371 assert len(tgt.sources) == 1, list(map(str, tgt.sources))
1372 assert str(tgt.sources[0]) == 'ggg.x.c', list(map(str, tgt.sources))
1374 tgt = b(env, target = None, source=['h0.c', 'h1.c'])[0]
1375 assert str(tgt) == 'h0.o', str(tgt)
1376 assert len(tgt.sources) == 2, list(map(str, tgt.sources))
1377 assert str(tgt.sources[0]) == 'h0.c', list(map(str, tgt.sources))
1378 assert str(tgt.sources[1]) == 'h1.c', list(map(str, tgt.sources))
1380 w = b(env, target='i0.w', source=['i0.x'])[0]
1381 y = b(env, target='i1.y', source=['i1.z'])[0]
1382 tgt = b(env, None, source=[w, y])[0]
1383 assert str(tgt) == 'i0.o', str(tgt)
1384 assert len(tgt.sources) == 2, list(map(str, tgt.sources))
1385 assert str(tgt.sources[0]) == 'i0.w', list(map(str, tgt.sources))
1386 assert str(tgt.sources[1]) == 'i1.y', list(map(str, tgt.sources))
1388 def test_get_name(self) -> None:
1389 """Test getting name of builder.
1391 Each type of builder should return its environment-specific
1392 name when queried appropriately. """
1394 b1 = SCons.Builder.Builder(action='foo', suffix='.o')
1395 b2 = SCons.Builder.Builder(action='foo', suffix='.c')
1396 b3 = SCons.Builder.Builder(action='bar', src_suffix = '.foo',
1397 src_builder = b1)
1398 b4 = SCons.Builder.Builder(action={})
1399 b5 = SCons.Builder.Builder(action='foo', name='builder5')
1400 b6 = SCons.Builder.Builder(action='foo')
1401 assert isinstance(b4, SCons.Builder.CompositeBuilder)
1402 assert isinstance(b4.action, SCons.Action.CommandGeneratorAction)
1404 env = Environment(BUILDERS={'bldr1': b1,
1405 'bldr2': b2,
1406 'bldr3': b3,
1407 'bldr4': b4})
1408 env2 = Environment(BUILDERS={'B1': b1,
1409 'B2': b2,
1410 'B3': b3,
1411 'B4': b4})
1412 # With no name, get_name will return the class. Allow
1413 # for caching...
1414 b6_names = [
1415 'SCons.Builder.BuilderBase',
1416 "<class 'SCons.Builder.BuilderBase'>",
1417 'SCons.Memoize.BuilderBase',
1418 "<class 'SCons.Memoize.BuilderBase'>",
1421 assert b1.get_name(env) == 'bldr1', b1.get_name(env)
1422 assert b2.get_name(env) == 'bldr2', b2.get_name(env)
1423 assert b3.get_name(env) == 'bldr3', b3.get_name(env)
1424 assert b4.get_name(env) == 'bldr4', b4.get_name(env)
1425 assert b5.get_name(env) == 'builder5', b5.get_name(env)
1426 assert b6.get_name(env) in b6_names, b6.get_name(env)
1428 assert b1.get_name(env2) == 'B1', b1.get_name(env2)
1429 assert b2.get_name(env2) == 'B2', b2.get_name(env2)
1430 assert b3.get_name(env2) == 'B3', b3.get_name(env2)
1431 assert b4.get_name(env2) == 'B4', b4.get_name(env2)
1432 assert b5.get_name(env2) == 'builder5', b5.get_name(env2)
1433 assert b6.get_name(env2) in b6_names, b6.get_name(env2)
1435 assert b5.get_name(None) == 'builder5', b5.get_name(None)
1436 assert b6.get_name(None) in b6_names, b6.get_name(None)
1438 # This test worked before adding batch builders, but we must now
1439 # be able to disambiguate a CompositeAction into a more specific
1440 # action based on file suffix at call time. Leave this commented
1441 # out (for now) in case this reflects a real-world use case that
1442 # we must accomodate and we want to resurrect this test.
1443 #tgt = b4(env, target = 'moo', source='cow')
1444 #assert tgt[0].builder.get_name(env) == 'bldr4'
1446 class CompositeBuilderTestCase(unittest.TestCase):
1448 def setUp(self) -> None:
1449 def func_action(target, source, env) -> int:
1450 return 0
1452 builder = SCons.Builder.Builder(action={ '.foo' : func_action,
1453 '.bar' : func_action})
1455 self.func_action = func_action
1456 self.builder = builder
1458 def test___init__(self) -> None:
1459 """Test CompositeBuilder creation"""
1460 env = Environment()
1461 builder = SCons.Builder.Builder(action={})
1463 tgt = builder(env, source=[])
1464 assert tgt == [], tgt
1466 assert isinstance(builder, SCons.Builder.CompositeBuilder)
1467 assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
1469 def test_target_action(self) -> None:
1470 """Test CompositeBuilder setting of target builder actions"""
1471 env = Environment()
1472 builder = self.builder
1474 tgt = builder(env, target='test1', source='test1.foo')[0]
1475 assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
1476 assert tgt.builder.action is builder.action
1478 tgt = builder(env, target='test2', source='test1.bar')[0]
1479 assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
1480 assert tgt.builder.action is builder.action
1482 def test_multiple_suffix_error(self) -> None:
1483 """Test the CompositeBuilder multiple-source-suffix error"""
1484 env = Environment()
1485 builder = self.builder
1487 flag = 0
1488 try:
1489 builder(env, target='test3', source=['test2.bar', 'test1.foo'])[0]
1490 except SCons.Errors.UserError as e:
1491 flag = 1
1492 err = e
1493 assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
1494 expect = "While building `['test3']' from `test1.foo': Cannot build multiple sources with different extensions: .bar, .foo"
1495 assert str(err) == expect, err
1497 def test_source_ext_match(self) -> None:
1498 """Test the CompositeBuilder source_ext_match argument"""
1499 env = Environment()
1500 func_action = self.func_action
1501 builder = SCons.Builder.Builder(action={ '.foo' : func_action,
1502 '.bar' : func_action},
1503 source_ext_match = None)
1505 tgt = builder(env, target='test3', source=['test2.bar', 'test1.foo'])[0]
1506 tgt.build()
1508 def test_suffix_variable(self) -> None:
1509 """Test CompositeBuilder defining action suffixes through a variable"""
1510 env = Environment(BAR_SUFFIX = '.BAR2', FOO_SUFFIX = '.FOO2')
1511 func_action = self.func_action
1512 builder = SCons.Builder.Builder(action={ '.foo' : func_action,
1513 '.bar' : func_action,
1514 '$BAR_SUFFIX' : func_action,
1515 '$FOO_SUFFIX' : func_action })
1517 tgt = builder(env, target='test4', source=['test4.BAR2'])[0]
1518 assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
1519 try:
1520 tgt.build()
1521 flag = 1
1522 except SCons.Errors.UserError as e:
1523 print(e)
1524 flag = 0
1525 assert flag, "It should be possible to define actions in composite builders using variables."
1526 env['FOO_SUFFIX'] = '.BAR2'
1527 builder.add_action('$NEW_SUFFIX', func_action)
1528 flag = 0
1529 try:
1530 builder(env, target='test5', source=['test5.BAR2'])[0]
1531 except SCons.Errors.UserError:
1532 flag = 1
1533 assert flag, "UserError should be thrown when we call a builder with ambigous suffixes."
1535 def test_src_builder(self) -> None:
1536 """Test CompositeBuilder's use of a src_builder"""
1537 env = Environment()
1539 foo_bld = SCons.Builder.Builder(action = 'a-foo',
1540 src_suffix = '.ina',
1541 suffix = '.foo')
1542 assert isinstance(foo_bld, SCons.Builder.BuilderBase)
1543 builder = SCons.Builder.Builder(action = { '.foo' : 'foo',
1544 '.bar' : 'bar' },
1545 src_builder = foo_bld)
1546 assert isinstance(builder, SCons.Builder.CompositeBuilder)
1547 assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
1549 tgt = builder(env, target='t1', source='t1a.ina t1b.ina')[0]
1550 assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
1552 tgt = builder(env, target='t2', source='t2a.foo t2b.ina')[0]
1553 assert isinstance(tgt.builder, SCons.Builder.BuilderBase), tgt.builder.__dict__
1555 bar_bld = SCons.Builder.Builder(action = 'a-bar',
1556 src_suffix = '.inb',
1557 suffix = '.bar')
1558 assert isinstance(bar_bld, SCons.Builder.BuilderBase)
1559 builder = SCons.Builder.Builder(action = { '.foo' : 'foo'},
1560 src_builder = [foo_bld, bar_bld])
1561 assert isinstance(builder, SCons.Builder.CompositeBuilder)
1562 assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
1564 builder.add_action('.bar', 'bar')
1566 tgt = builder(env, target='t3-foo', source='t3a.foo t3b.ina')[0]
1567 assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
1569 tgt = builder(env, target='t3-bar', source='t3a.bar t3b.inb')[0]
1570 assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
1572 flag = 0
1573 try:
1574 builder(env, target='t5', source=['test5a.foo', 'test5b.inb'])[0]
1575 except SCons.Errors.UserError as e:
1576 flag = 1
1577 err = e
1578 assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
1579 expect = "While building `['t5']' from `test5b.bar': Cannot build multiple sources with different extensions: .foo, .bar"
1580 assert str(err) == expect, err
1582 flag = 0
1583 try:
1584 builder(env, target='t6', source=['test6a.bar', 'test6b.ina'])[0]
1585 except SCons.Errors.UserError as e:
1586 flag = 1
1587 err = e
1588 assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
1589 expect = "While building `['t6']' from `test6b.foo': Cannot build multiple sources with different extensions: .bar, .foo"
1590 assert str(err) == expect, err
1592 flag = 0
1593 try:
1594 builder(env, target='t4', source=['test4a.ina', 'test4b.inb'])[0]
1595 except SCons.Errors.UserError as e:
1596 flag = 1
1597 err = e
1598 assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
1599 expect = "While building `['t4']' from `test4b.bar': Cannot build multiple sources with different extensions: .foo, .bar"
1600 assert str(err) == expect, err
1602 flag = 0
1603 try:
1604 builder(env, target='t7', source=[env.fs.File('test7')])[0]
1605 except SCons.Errors.UserError as e:
1606 flag = 1
1607 err = e
1608 assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
1609 expect = "While building `['t7']': Cannot deduce file extension from source files: ['test7']"
1610 assert str(err) == expect, err
1612 flag = 0
1613 try:
1614 builder(env, target='t8', source=['test8.unknown'])[0]
1615 except SCons.Errors.UserError as e:
1616 flag = 1
1617 err = e
1618 assert flag, "UserError should be thrown when we call a builder target with an unknown suffix."
1619 expect = "While building `['t8']' from `['test8.unknown']': Don't know how to build from a source file with suffix `.unknown'. Expected a suffix in this list: ['.foo', '.bar']."
1620 assert str(err) == expect, err
1622 if __name__ == "__main__":
1623 unittest.main()
1625 # Local Variables:
1626 # tab-width:4
1627 # indent-tabs-mode:nil
1628 # End:
1629 # vim: set expandtab tabstop=4 shiftwidth=4: