Return master to development mode post release
[scons.git] / SCons / BuilderTests.py
blobb66f52439e415baa9a1403dc54071dd76023c8e1
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
48 from SCons.Util.sctyping import ExecutorType
50 sys.stdout = io.StringIO()
52 # Initial setup of the common environment for all tests,
53 # a temporary working directory containing a
54 # script for writing arguments to an output file.
56 # We don't do this as a setUp() method because it's
57 # unnecessary to create a separate directory and script
58 # for each test, they can just use the one.
59 test = TestCmd.TestCmd(workdir = '')
61 outfile = test.workpath('outfile')
62 outfile2 = test.workpath('outfile2')
64 infile = test.workpath('infile')
65 test.write(infile, "infile\n")
67 show_string = None
69 scons_env = SCons.Environment.Environment()
71 env_arg2nodes_called = None
73 class Environment:
74 def __init__(self, **kw) -> None:
75 self.d = {}
76 self.d['SHELL'] = scons_env['SHELL']
77 self.d['SPAWN'] = scons_env['SPAWN']
78 self.d['ESCAPE'] = scons_env['ESCAPE']
79 for k, v in kw.items():
80 self.d[k] = v
81 global env_arg2nodes_called
82 env_arg2nodes_called = None
83 self.scanner = None
84 self.fs = SCons.Node.FS.FS()
85 def subst(self, s):
86 if not SCons.Util.is_String(s):
87 return s
88 def substitute(m, d=self.d):
89 return d.get(m.group(1), '')
90 return re.sub(r'\$(\w+)', substitute, s)
91 def subst_target_source(self, string, raw: int=0, target=None,
92 source=None, dict=None, conv=None):
93 return SCons.Subst.scons_subst(string, self, raw, target,
94 source, dict, conv)
95 def subst_list(self, string, raw: int=0, target=None, source=None, conv=None):
96 return SCons.Subst.scons_subst_list(string, self, raw, target,
97 source, {}, {}, conv)
98 def arg2nodes(self, args, factory, **kw):
99 global env_arg2nodes_called
100 env_arg2nodes_called = 1
101 if not SCons.Util.is_List(args):
102 args = [args]
103 list = []
104 for a in args:
105 if SCons.Util.is_String(a):
106 a = factory(self.subst(a))
107 list.append(a)
108 return list
109 def get_factory(self, factory):
110 return factory or self.fs.File
111 def get_scanner(self, ext):
112 return self.scanner
113 def Dictionary(self):
114 return {}
115 def autogenerate(self, dir: str=''):
116 return {}
117 def __setitem__(self, item, var) -> None:
118 self.d[item] = var
119 def __getitem__(self, item):
120 return self.d[item]
121 def __contains__(self, key) -> bool:
122 return key in self.d
123 def keys(self):
124 return list(self.d.keys())
125 def get(self, key, value=None):
126 return self.d.get(key, value)
127 def Override(self, overrides):
128 env = Environment(**self.d)
129 env.d.update(overrides)
130 env.scanner = self.scanner
131 return env
132 def _update(self, dict) -> None:
133 self.d.update(dict)
134 def items(self):
135 return list(self.d.items())
136 def sig_dict(self):
137 d = {}
138 for k,v in self.items(): d[k] = v
139 d['TARGETS'] = ['__t1__', '__t2__', '__t3__', '__t4__', '__t5__', '__t6__']
140 d['TARGET'] = d['TARGETS'][0]
141 d['SOURCES'] = ['__s1__', '__s2__', '__s3__', '__s4__', '__s5__', '__s6__']
142 d['SOURCE'] = d['SOURCES'][0]
143 return d
144 def __eq__(self, other):
145 return self.scanner == other.scanner or self.d == other.d
147 class MyAction:
148 def __init__(self, action) -> None:
149 self.action = action
150 def __call__(self, *args, **kw) -> None:
151 pass
152 def get_executor(self, env, overrides, tlist, slist, executor_kw):
153 return ['executor'] + [self.action]
155 class MyNode_without_target_from_source:
156 def __init__(self, name) -> None:
157 self.name = name
158 self.sources = []
159 self.builder = None
160 self.is_explicit = None
161 self.side_effect = 0
162 def get_suffix(self):
163 return os.path.splitext(self.name)[1]
164 def disambiguate(self):
165 return self
166 def __str__(self) -> str:
167 return self.name
168 def builder_set(self, builder) -> None:
169 self.builder = builder
170 def has_builder(self) -> bool:
171 return self.builder is not None
172 def set_explicit(self, is_explicit) -> None:
173 self.is_explicit = is_explicit
174 def has_explicit_builder(self) -> bool:
175 return self.is_explicit
176 def env_set(self, env, safe: bool=False) -> None:
177 self.env = env
178 def add_source(self, source) -> None:
179 self.sources.extend(source)
180 def scanner_key(self):
181 return self.name
182 def is_derived(self) -> bool:
183 return self.has_builder()
184 def generate_build_env(self, env):
185 return env
186 def get_build_env(self):
187 return self.executor.get_build_env()
188 def set_executor(self, executor: ExecutorType) -> None:
189 self.executor = executor
190 def get_executor(self, create: int=1) -> ExecutorType:
191 return self.executor
193 class MyNode(MyNode_without_target_from_source):
194 def target_from_source(self, prefix, suffix, stripext):
195 return MyNode(prefix + stripext(str(self))[0] + suffix)
197 class BuilderTestCase(unittest.TestCase):
199 def test__init__(self) -> None:
200 """Test simple Builder creation
202 builder = SCons.Builder.Builder(action="foo")
203 assert builder is not None, builder
204 builder = SCons.Builder.Builder(action="foo", OVERRIDE='x')
205 x = builder.overrides['OVERRIDE']
206 assert x == 'x', x
208 def test__bool__(self) -> None:
209 """Test a builder raising an exception when __bool__ is called. """
211 # basic test: explicitly call it
212 builder = SCons.Builder.Builder(action="foo")
213 exc_caught = None
214 try:
215 builder.__bool__()
216 except SCons.Errors.InternalError:
217 exc_caught = 1
218 assert exc_caught, "did not catch expected InternalError exception"
220 class Node:
221 pass
223 # the interesting test: checking the attribute this way
224 # should call back to Builder.__bool__ - so we can tell
225 # people not to check that way (for performance).
226 # TODO: workaround #3860: with just a "pass" in the check body,
227 # py3.10a optimizes out the whole thing, so add a "real" stmt
228 n = Node()
229 n.builder = builder
230 exc_caught = None
231 try:
232 if n.builder:
233 #pass
234 _ = True
235 except SCons.Errors.InternalError:
236 exc_caught = 1
237 assert exc_caught, "did not catch expected InternalError exception"
239 def test__call__(self):
240 """Test calling a builder to establish source dependencies
242 env = Environment()
243 builder = SCons.Builder.Builder(action="foo",
244 target_factory=MyNode,
245 source_factory=MyNode)
247 tgt = builder(env, source=[])
248 assert tgt == [], tgt
250 n1 = MyNode("n1")
251 n2 = MyNode("n2")
252 builder(env, target = n1, source = n2)
253 assert env_arg2nodes_called
254 assert n1.env == env, n1.env
255 assert n1.builder == builder, n1.builder
256 assert n1.sources == [n2], n1.sources
257 assert n1.executor, "no executor found"
258 assert not hasattr(n2, 'env')
260 l = [1]
261 ul = UserList([2])
262 try:
263 l.extend(ul)
264 except TypeError:
265 def mystr(l):
266 return str(list(map(str, l)))
267 else:
268 mystr = str
270 nnn1 = MyNode("nnn1")
271 nnn2 = MyNode("nnn2")
272 tlist = builder(env, target = [nnn1, nnn2], source = [])
273 s = mystr(tlist)
274 assert s == "['nnn1', 'nnn2']", s
275 l = list(map(str, tlist))
276 assert l == ['nnn1', 'nnn2'], l
278 tlist = builder(env, target = 'n3', source = 'n4')
279 s = mystr(tlist)
280 assert s == "['n3']", s
281 target = tlist[0]
282 l = list(map(str, tlist))
283 assert l == ['n3'], l
284 assert target.name == 'n3'
285 assert target.sources[0].name == 'n4'
287 tlist = builder(env, target = 'n4 n5', source = ['n6 n7'])
288 s = mystr(tlist)
289 assert s == "['n4 n5']", s
290 l = list(map(str, tlist))
291 assert l == ['n4 n5'], l
292 target = tlist[0]
293 assert target.name == 'n4 n5'
294 assert target.sources[0].name == 'n6 n7'
296 tlist = builder(env, target = ['n8 n9'], source = 'n10 n11')
297 s = mystr(tlist)
298 assert s == "['n8 n9']", s
299 l = list(map(str, tlist))
300 assert l == ['n8 n9'], l
301 target = tlist[0]
302 assert target.name == 'n8 n9'
303 assert target.sources[0].name == 'n10 n11'
305 # A test to be uncommented when we freeze the environment
306 # as part of calling the builder.
307 #env1 = Environment(VAR='foo')
308 #target = builder(env1, target = 'n12', source = 'n13')
309 #env1['VAR'] = 'bar'
310 #be = target.get_build_env()
311 #assert be['VAR'] == 'foo', be['VAR']
313 n20 = MyNode_without_target_from_source('n20')
314 flag = 0
315 try:
316 target = builder(env, None, source=n20)
317 except SCons.Errors.UserError as e:
318 flag = 1
319 assert flag, "UserError should be thrown if a source node can't create a target."
321 builder = SCons.Builder.Builder(action="foo",
322 target_factory=MyNode,
323 source_factory=MyNode,
324 prefix='p-',
325 suffix='.s')
326 target = builder(env, None, source='n21')[0]
327 assert target.name == 'p-n21.s', target
329 builder = SCons.Builder.Builder(misspelled_action="foo",
330 suffix = '.s')
331 try:
332 builder(env, target = 'n22', source = 'n22')
333 except SCons.Errors.UserError as e:
334 pass
335 else:
336 raise Exception("Did not catch expected UserError.")
338 builder = SCons.Builder.Builder(action="foo")
339 target = builder(env, None, source='n22', srcdir='src_dir')[0]
340 p = target.sources[0].get_internal_path()
341 assert p == os.path.join('src_dir', 'n22'), p
343 def test_mistaken_variables(self) -> None:
344 """Test keyword arguments that are often mistakes
346 import SCons.Warnings
347 env = Environment()
348 builder = SCons.Builder.Builder(action="foo")
350 save_warn = SCons.Warnings.warn
351 warned = []
352 def my_warn(exception, warning, warned=warned) -> None:
353 warned.append(warning)
354 SCons.Warnings.warn = my_warn
356 try:
357 target = builder(env, 'mistaken1', sources='mistaken1.c')
358 assert warned == ["Did you mean to use `source' instead of `sources'?"], warned
359 del warned[:]
361 target = builder(env, 'mistaken2', targets='mistaken2.c')
362 assert warned == ["Did you mean to use `target' instead of `targets'?"], warned
363 del warned[:]
365 target = builder(env, 'mistaken3', targets='mistaken3', sources='mistaken3.c')
366 assert "Did you mean to use `source' instead of `sources'?" in warned, warned
367 assert "Did you mean to use `target' instead of `targets'?" in warned, warned
368 del warned[:]
369 finally:
370 SCons.Warnings.warn = save_warn
372 def test_action(self) -> None:
373 """Test Builder creation
375 Verify that we can retrieve the supplied action attribute.
377 builder = SCons.Builder.Builder(action="foo")
378 assert builder.action.cmd_list == "foo"
380 def func() -> None:
381 pass
382 builder = SCons.Builder.Builder(action=func)
383 assert isinstance(builder.action, SCons.Action.FunctionAction)
384 # Preserve the following so that the baseline test will fail.
385 # Remove it in favor of the previous test at some convenient
386 # point in the future.
387 assert builder.action.execfunction == func
389 def test_generator(self) -> None:
390 """Test Builder creation given a generator function."""
392 def generator() -> None:
393 pass
395 builder = SCons.Builder.Builder(generator=generator)
396 assert builder.action.generator == generator
398 def test_cmp(self) -> None:
399 """Test simple comparisons of Builder objects
401 b1 = SCons.Builder.Builder(src_suffix = '.o')
402 b2 = SCons.Builder.Builder(src_suffix = '.o')
403 assert b1 == b2
404 b3 = SCons.Builder.Builder(src_suffix = '.x')
405 assert b1 != b3
406 assert b2 != b3
408 def test_target_factory(self) -> None:
409 """Test a Builder that creates target nodes of a specified class
411 class Foo:
412 pass
413 def FooFactory(target):
414 global Foo
415 return Foo(target)
416 builder = SCons.Builder.Builder(target_factory = FooFactory)
417 assert builder.target_factory is FooFactory
418 assert builder.source_factory is not FooFactory
420 def test_source_factory(self) -> None:
421 """Test a Builder that creates source nodes of a specified class
423 class Foo:
424 pass
425 def FooFactory(source):
426 global Foo
427 return Foo(source)
428 builder = SCons.Builder.Builder(source_factory = FooFactory)
429 assert builder.target_factory is not FooFactory
430 assert builder.source_factory is FooFactory
432 def test_splitext(self) -> None:
433 """Test the splitext() method attached to a Builder."""
434 b = SCons.Builder.Builder()
435 assert b.splitext('foo') == ('foo','')
436 assert b.splitext('foo.bar') == ('foo','.bar')
437 assert b.splitext(os.path.join('foo.bar', 'blat')) == (os.path.join('foo.bar', 'blat'),'')
439 class MyBuilder(SCons.Builder.BuilderBase):
440 def splitext(self, path) -> str:
441 return "called splitext()"
443 b = MyBuilder()
444 ret = b.splitext('xyz.c')
445 assert ret == "called splitext()", ret
447 def test_adjust_suffix(self) -> None:
448 """Test how a Builder adjusts file suffixes
450 b = SCons.Builder.Builder()
451 assert b.adjust_suffix('.foo') == '.foo'
452 assert b.adjust_suffix('foo') == '.foo'
453 assert b.adjust_suffix('$foo') == '$foo'
455 class MyBuilder(SCons.Builder.BuilderBase):
456 def adjust_suffix(self, suff) -> str:
457 return "called adjust_suffix()"
459 b = MyBuilder()
460 ret = b.adjust_suffix('.foo')
461 assert ret == "called adjust_suffix()", ret
463 def test_prefix(self) -> None:
464 """Test Builder creation with a specified target prefix
466 Make sure that there is no '.' separator appended.
468 env = Environment()
469 builder = SCons.Builder.Builder(prefix = 'lib.')
470 assert builder.get_prefix(env) == 'lib.'
471 builder = SCons.Builder.Builder(prefix = 'lib', action='')
472 assert builder.get_prefix(env) == 'lib'
473 tgt = builder(env, target = 'tgt1', source = 'src1')[0]
474 assert tgt.get_internal_path() == 'libtgt1', \
475 "Target has unexpected name: %s" % tgt.get_internal_path()
476 tgt = builder(env, target = 'tgt2a tgt2b', source = 'src2')[0]
477 assert tgt.get_internal_path() == 'libtgt2a tgt2b', \
478 "Target has unexpected name: %s" % tgt.get_internal_path()
479 tgt = builder(env, target = None, source = 'src3')[0]
480 assert tgt.get_internal_path() == 'libsrc3', \
481 "Target has unexpected name: %s" % tgt.get_internal_path()
482 tgt = builder(env, target = None, source = 'lib/src4')[0]
483 assert tgt.get_internal_path() == os.path.join('lib', 'libsrc4'), \
484 "Target has unexpected name: %s" % tgt.get_internal_path()
485 tgt = builder(env, target = 'lib/tgt5', source = 'lib/src5')[0]
486 assert tgt.get_internal_path() == os.path.join('lib', 'libtgt5'), \
487 "Target has unexpected name: %s" % tgt.get_internal_path()
489 def gen_prefix(env, sources):
490 return "gen_prefix() says " + env['FOO']
491 my_env = Environment(FOO = 'xyzzy')
492 builder = SCons.Builder.Builder(prefix = gen_prefix)
493 assert builder.get_prefix(my_env) == "gen_prefix() says xyzzy"
494 my_env['FOO'] = 'abracadabra'
495 assert builder.get_prefix(my_env) == "gen_prefix() says abracadabra"
497 def my_emit(env, sources):
498 return env.subst('$EMIT')
499 my_env = Environment(FOO = '.foo', EMIT = 'emit-')
500 builder = SCons.Builder.Builder(prefix = {None : 'default-',
501 '.in' : 'out-',
502 '.x' : 'y-',
503 '$FOO' : 'foo-',
504 '.zzz' : my_emit},
505 action = '')
506 tgt = builder(my_env, target = None, source = 'f1')[0]
507 assert tgt.get_internal_path() == 'default-f1', tgt.get_internal_path()
508 tgt = builder(my_env, target = None, source = 'f2.c')[0]
509 assert tgt.get_internal_path() == 'default-f2', tgt.get_internal_path()
510 tgt = builder(my_env, target = None, source = 'f3.in')[0]
511 assert tgt.get_internal_path() == 'out-f3', tgt.get_internal_path()
512 tgt = builder(my_env, target = None, source = 'f4.x')[0]
513 assert tgt.get_internal_path() == 'y-f4', tgt.get_internal_path()
514 tgt = builder(my_env, target = None, source = 'f5.foo')[0]
515 assert tgt.get_internal_path() == 'foo-f5', tgt.get_internal_path()
516 tgt = builder(my_env, target = None, source = 'f6.zzz')[0]
517 assert tgt.get_internal_path() == 'emit-f6', tgt.get_internal_path()
519 def test_set_suffix(self) -> None:
520 """Test the set_suffix() method"""
521 b = SCons.Builder.Builder(action='')
522 env = Environment(XSUFFIX = '.x')
524 s = b.get_suffix(env)
525 assert s == '', s
527 b.set_suffix('.foo')
528 s = b.get_suffix(env)
529 assert s == '.foo', s
531 b.set_suffix('$XSUFFIX')
532 s = b.get_suffix(env)
533 assert s == '.x', s
535 def test_src_suffix(self) -> None:
536 """Test Builder creation with a specified source file suffix
538 Make sure that the '.' separator is appended to the
539 beginning if it isn't already present.
541 env = Environment(XSUFFIX = '.x', YSUFFIX = '.y')
543 b1 = SCons.Builder.Builder(src_suffix = '.c', action='')
544 assert b1.src_suffixes(env) == ['.c'], b1.src_suffixes(env)
546 tgt = b1(env, target = 'tgt2', source = 'src2')[0]
547 assert tgt.sources[0].get_internal_path() == 'src2.c', \
548 "Source has unexpected name: %s" % tgt.sources[0].get_internal_path()
550 tgt = b1(env, target = 'tgt3', source = 'src3a src3b')[0]
551 assert len(tgt.sources) == 1
552 assert tgt.sources[0].get_internal_path() == 'src3a src3b.c', \
553 "Unexpected tgt.sources[0] name: %s" % tgt.sources[0].get_internal_path()
555 b2 = SCons.Builder.Builder(src_suffix = '.2', src_builder = b1)
556 r = sorted(b2.src_suffixes(env))
557 assert r == ['.2', '.c'], r
559 b3 = SCons.Builder.Builder(action = {'.3a' : '', '.3b' : ''})
560 s = sorted(b3.src_suffixes(env))
561 assert s == ['.3a', '.3b'], s
563 b4 = SCons.Builder.Builder(src_suffix = '$XSUFFIX')
564 assert b4.src_suffixes(env) == ['.x'], b4.src_suffixes(env)
566 b5 = SCons.Builder.Builder(action = { '.y' : ''})
567 assert b5.src_suffixes(env) == ['.y'], b5.src_suffixes(env)
569 def test_srcsuffix_nonext(self) -> None:
570 """Test target generation from non-extension source suffixes"""
571 env = Environment()
572 b6 = SCons.Builder.Builder(action = '',
573 src_suffix='_src.a',
574 suffix='.b')
575 tgt = b6(env, target=None, source='foo_src.a')
576 assert str(tgt[0]) == 'foo.b', str(tgt[0])
578 b7 = SCons.Builder.Builder(action = '',
579 src_suffix='_source.a',
580 suffix='_obj.b')
581 b8 = SCons.Builder.Builder(action = '',
582 src_builder=b7,
583 suffix='.c')
584 tgt = b8(env, target=None, source='foo_source.a')
585 assert str(tgt[0]) == 'foo_obj.c', str(tgt[0])
586 src = env.fs.File('foo_source.a')
587 tgt = b8(env, target=None, source=src)
588 assert str(tgt[0]) == 'foo_obj.c', str(tgt[0])
590 b9 = SCons.Builder.Builder(action={'_src.a' : 'srcaction'},
591 suffix='.c')
592 b9.add_action('_altsrc.b', 'altaction')
593 tgt = b9(env, target=None, source='foo_altsrc.b')
594 assert str(tgt[0]) == 'foo.c', str(tgt[0])
596 def test_src_suffix_expansion(self) -> None:
597 """Test handling source suffixes when an expansion is involved"""
598 env = Environment(OBJSUFFIX = '.obj')
600 b1 = SCons.Builder.Builder(action = '',
601 src_suffix='.c',
602 suffix='.obj')
603 b2 = SCons.Builder.Builder(action = '',
604 src_builder=b1,
605 src_suffix='.obj',
606 suffix='.exe')
607 tgt = b2(env, target=None, source=['foo$OBJSUFFIX'])
608 s = list(map(str, tgt[0].sources))
609 assert s == ['foo.obj'], s
611 def test_suffix(self) -> None:
612 """Test Builder creation with a specified target suffix
614 Make sure that the '.' separator is appended to the
615 beginning if it isn't already present.
617 env = Environment()
618 builder = SCons.Builder.Builder(suffix = '.o')
619 assert builder.get_suffix(env) == '.o', builder.get_suffix(env)
620 builder = SCons.Builder.Builder(suffix = 'o', action='')
621 assert builder.get_suffix(env) == '.o', builder.get_suffix(env)
622 tgt = builder(env, target = 'tgt3', source = 'src3')[0]
623 assert tgt.get_internal_path() == 'tgt3.o', \
624 "Target has unexpected name: %s" % tgt.get_internal_path()
625 tgt = builder(env, target = 'tgt4a tgt4b', source = 'src4')[0]
626 assert tgt.get_internal_path() == 'tgt4a tgt4b.o', \
627 "Target has unexpected name: %s" % tgt.get_internal_path()
628 tgt = builder(env, target = None, source = 'src5')[0]
629 assert tgt.get_internal_path() == 'src5.o', \
630 "Target has unexpected name: %s" % tgt.get_internal_path()
632 def gen_suffix(env, sources):
633 return "gen_suffix() says " + env['BAR']
634 my_env = Environment(BAR = 'hocus pocus')
635 builder = SCons.Builder.Builder(suffix = gen_suffix)
636 assert builder.get_suffix(my_env) == "gen_suffix() says hocus pocus", builder.get_suffix(my_env)
637 my_env['BAR'] = 'presto chango'
638 assert builder.get_suffix(my_env) == "gen_suffix() says presto chango"
640 def my_emit(env, sources):
641 return env.subst('$EMIT')
642 my_env = Environment(BAR = '.bar', EMIT = '.emit')
643 builder = SCons.Builder.Builder(suffix = {None : '.default',
644 '.in' : '.out',
645 '.x' : '.y',
646 '$BAR' : '.new',
647 '.zzz' : my_emit},
648 action='')
649 tgt = builder(my_env, target = None, source = 'f1')[0]
650 assert tgt.get_internal_path() == 'f1.default', tgt.get_internal_path()
651 tgt = builder(my_env, target = None, source = 'f2.c')[0]
652 assert tgt.get_internal_path() == 'f2.default', tgt.get_internal_path()
653 tgt = builder(my_env, target = None, source = 'f3.in')[0]
654 assert tgt.get_internal_path() == 'f3.out', tgt.get_internal_path()
655 tgt = builder(my_env, target = None, source = 'f4.x')[0]
656 assert tgt.get_internal_path() == 'f4.y', tgt.get_internal_path()
657 tgt = builder(my_env, target = None, source = 'f5.bar')[0]
658 assert tgt.get_internal_path() == 'f5.new', tgt.get_internal_path()
659 tgt = builder(my_env, target = None, source = 'f6.zzz')[0]
660 assert tgt.get_internal_path() == 'f6.emit', tgt.get_internal_path()
662 def test__adjustixes(self) -> None:
663 """Test the _adjustixes() method"""
664 from pathlib import Path
666 builder = SCons.Builder.Builder()
668 # path without suffix should have both added
669 with self.subTest():
670 r = builder._adjustixes('file', 'pre-', '-suf')
671 self.assertEqual(r, ['pre-file-suf'])
673 # path with a suffix should not have one added
674 with self.subTest():
675 r = builder._adjustixes('file.o', 'pre-', '-suf')
676 self.assertEqual(r, ['pre-file.o'])
678 # unless ensure_suffix is True
679 with self.subTest():
680 r = builder._adjustixes('file.o', 'pre-', '-suf', ensure_suffix=True)
681 self.assertEqual(r, ['pre-file.o-suf'])
683 # a PathLike object should be modified
684 with self.subTest():
685 r = builder._adjustixes(Path('file'), 'pre-', '-suf')
686 self.assertEqual(r, ['pre-file-suf'])
688 # make sure a non-str, non-PathLike is just left alone
689 with self.subTest():
690 env = Environment()
691 mynode = env.fs.File('file')
692 r = builder._adjustixes(mynode, 'pre-', '-suf')
693 self.assertEqual(r, [mynode])
694 self.assertEqual(r[0].path, 'file')
696 def test_single_source(self) -> None:
697 """Test Builder with single_source flag set"""
698 def func(target, source, env) -> None:
699 """create the file"""
700 with open(str(target[0]), "w"):
701 pass
702 if len(source) == 1 and len(target) == 1:
703 env['CNT'][0] = env['CNT'][0] + 1
705 env = Environment()
706 infiles = []
707 outfiles = []
708 for i in range(10):
709 infiles.append(test.workpath('%d.in' % i))
710 outfiles.append(test.workpath('%d.out' % i))
711 test.write(infiles[-1], "\n")
712 builder = SCons.Builder.Builder(action=SCons.Action.Action(func,None),
713 single_source = 1, suffix='.out')
714 env['CNT'] = [0]
715 tgt = builder(env, target=outfiles[0], source=infiles[0])[0]
716 s = str(tgt)
717 t = os.path.normcase(test.workpath('0.out'))
718 assert os.path.normcase(s) == t, s
719 tgt.prepare()
720 tgt.build()
721 assert env['CNT'][0] == 1, env['CNT'][0]
722 tgt = builder(env, outfiles[1], infiles[1])[0]
723 s = str(tgt)
724 t = os.path.normcase(test.workpath('1.out'))
725 assert os.path.normcase(s) == t, s
726 tgt.prepare()
727 tgt.build()
728 assert env['CNT'][0] == 2
729 tgts = builder(env, None, infiles[2:4])
730 s = list(map(str, tgts))
731 expect = [test.workpath('2.out'), test.workpath('3.out')]
732 expect = list(map(os.path.normcase, expect))
733 assert list(map(os.path.normcase, s)) == expect, s
734 for t in tgts: t.prepare()
735 tgts[0].build()
736 tgts[1].build()
737 assert env['CNT'][0] == 4
738 try:
739 tgt = builder(env, outfiles[4], infiles[4:6])
740 except SCons.Errors.UserError:
741 pass
742 else:
743 assert 0
744 try:
745 # The builder may output more than one target per input file.
746 tgt = builder(env, outfiles[4:6], infiles[4:6])
747 except SCons.Errors.UserError:
748 pass
749 else:
750 assert 0
753 def test_lists(self) -> None:
754 """Testing handling lists of targets and source"""
755 def function2(target, source, env, tlist = [outfile, outfile2], **kw) -> int:
756 for t in target:
757 with open(str(t), 'w') as f:
758 f.write("function2\n")
759 for t in tlist:
760 if t not in list(map(str, target)):
761 with open(t, 'w') as f:
762 f.write("function2\n")
763 return 1
765 env = Environment()
766 builder = SCons.Builder.Builder(action = function2)
768 tgts = builder(env, source=[])
769 assert tgts == [], tgts
771 tgts = builder(env, target = [outfile, outfile2], source = infile)
772 for t in tgts:
773 t.prepare()
774 try:
775 tgts[0].build()
776 except SCons.Errors.BuildError:
777 pass
778 c = test.read(outfile, 'r')
779 assert c == "function2\n", c
780 c = test.read(outfile2, 'r')
781 assert c == "function2\n", c
783 sub1_out = test.workpath('sub1', 'out')
784 sub2_out = test.workpath('sub2', 'out')
786 def function3(target, source, env, tlist = [sub1_out, sub2_out]) -> int:
787 for t in target:
788 with open(str(t), 'w') as f:
789 f.write("function3\n")
790 for t in tlist:
791 if t not in list(map(str, target)):
792 with open(t, 'w') as f:
793 f.write("function3\n")
794 return 1
796 builder = SCons.Builder.Builder(action = function3)
797 tgts = builder(env, target = [sub1_out, sub2_out], source = infile)
798 for t in tgts:
799 t.prepare()
800 try:
801 tgts[0].build()
802 except SCons.Errors.BuildError:
803 pass
804 c = test.read(sub1_out, 'r')
805 assert c == "function3\n", c
806 c = test.read(sub2_out, 'r')
807 assert c == "function3\n", c
808 assert os.path.exists(test.workpath('sub1'))
809 assert os.path.exists(test.workpath('sub2'))
811 def test_src_builder(self) -> None:
812 """Testing Builders with src_builder"""
813 # These used to be MultiStepBuilder objects until we
814 # eliminated it as a separate class
815 env = Environment()
816 builder1 = SCons.Builder.Builder(action='foo',
817 src_suffix='.bar',
818 suffix='.foo')
819 builder2 = SCons.Builder.Builder(action=MyAction('act'),
820 src_builder = builder1,
821 src_suffix = '.foo')
823 tgt = builder2(env, source=[])
824 assert tgt == [], tgt
826 sources = ['test.bar', 'test2.foo', 'test3.txt', 'test4']
827 tgt = builder2(env, target='baz', source=sources)[0]
828 s = str(tgt)
829 assert s == 'baz', s
830 s = list(map(str, tgt.sources))
831 assert s == ['test.foo', 'test2.foo', 'test3.txt', 'test4.foo'], s
832 s = list(map(str, tgt.sources[0].sources))
833 assert s == ['test.bar'], s
835 tgt = builder2(env, None, 'aaa.bar')[0]
836 s = str(tgt)
837 assert s == 'aaa', s
838 s = list(map(str, tgt.sources))
839 assert s == ['aaa.foo'], s
840 s = list(map(str, tgt.sources[0].sources))
841 assert s == ['aaa.bar'], s
843 builder3 = SCons.Builder.Builder(action='bld3')
844 assert builder3.src_builder is not builder1.src_builder
846 builder4 = SCons.Builder.Builder(action='bld4',
847 src_suffix='.i',
848 suffix='_wrap.c')
849 builder5 = SCons.Builder.Builder(action=MyAction('act'),
850 src_builder=builder4,
851 suffix='.obj',
852 src_suffix='.c')
853 builder6 = SCons.Builder.Builder(action=MyAction('act'),
854 src_builder=builder5,
855 suffix='.exe',
856 src_suffix='.obj')
857 tgt = builder6(env, 'test', 'test.i')[0]
858 s = str(tgt)
859 assert s == 'test.exe', s
860 s = list(map(str, tgt.sources))
861 assert s == ['test_wrap.obj'], s
862 s = list(map(str, tgt.sources[0].sources))
863 assert s == ['test_wrap.c'], s
864 s = list(map(str, tgt.sources[0].sources[0].sources))
865 assert s == ['test.i'], s
867 def test_target_scanner(self) -> None:
868 """Testing ability to set target and source scanners through a builder."""
869 global instanced
870 class TestScanner:
871 pass
872 tscan = TestScanner()
873 sscan = TestScanner()
874 env = Environment()
875 builder = SCons.Builder.Builder(target_scanner=tscan,
876 source_scanner=sscan,
877 action='')
878 tgt = builder(env, target='foo2', source='bar')[0]
879 assert tgt.builder.target_scanner == tscan, tgt.builder.target_scanner
880 assert tgt.builder.source_scanner == sscan, tgt.builder.source_scanner
882 builder1 = SCons.Builder.Builder(action='foo',
883 src_suffix='.bar',
884 suffix='.foo')
885 builder2 = SCons.Builder.Builder(action='foo',
886 src_builder = builder1,
887 target_scanner = tscan,
888 source_scanner = tscan)
889 tgt = builder2(env, target='baz2', source='test.bar test2.foo test3.txt')[0]
890 assert tgt.builder.target_scanner == tscan, tgt.builder.target_scanner
891 assert tgt.builder.source_scanner == tscan, tgt.builder.source_scanner
893 def test_actual_scanner(self) -> None:
894 """Test usage of actual Scanner objects."""
896 import SCons.Scanner
898 def func(self) -> None:
899 pass
901 scanner = SCons.Scanner.ScannerBase(func, name='fooscan')
903 b1 = SCons.Builder.Builder(action='bld', target_scanner=scanner)
904 b2 = SCons.Builder.Builder(action='bld', target_scanner=scanner)
905 b3 = SCons.Builder.Builder(action='bld')
907 assert b1 == b2
908 assert b1 != b3
910 def test_src_scanner(self) -> None:
911 """Testing ability to set a source file scanner through a builder."""
912 class TestScanner:
913 def key(self, env) -> str:
914 return 'TestScannerkey'
915 def instance(self, env):
916 return self
917 def select(self, node):
918 return self
919 name = 'TestScanner'
920 def __str__(self) -> str:
921 return self.name
923 scanner = TestScanner()
924 builder = SCons.Builder.Builder(action='action')
926 # With no scanner specified, source_scanner and
927 # backup_source_scanner are None.
928 bar_y = MyNode('bar.y')
929 env1 = Environment()
930 tgt = builder(env1, target='foo1.x', source='bar.y')[0]
931 src = tgt.sources[0]
932 assert tgt.builder.target_scanner != scanner, tgt.builder.target_scanner
933 assert tgt.builder.source_scanner is None, tgt.builder.source_scanner
934 assert tgt.get_source_scanner(bar_y) is None, tgt.get_source_scanner(bar_y)
935 assert not src.has_builder(), src.has_builder()
936 s = src.get_source_scanner(bar_y)
937 assert isinstance(s, SCons.Util.Null), repr(s)
939 # An Environment that has suffix-specified SCANNERS should
940 # provide a source scanner to the target.
941 class EnvTestScanner:
942 def key(self, env) -> str:
943 return '.y'
944 def instance(self, env):
945 return self
946 name = 'EnvTestScanner'
947 def __str__(self) -> str:
948 return self.name
949 def select(self, node):
950 return self
951 def path(self, env, dir=None):
952 return ()
953 def __call__(self, node, env, path):
954 return []
955 env3 = Environment(SCANNERS = [EnvTestScanner()])
956 env3.scanner = EnvTestScanner() # test env's version of SCANNERS
957 tgt = builder(env3, target='foo2.x', source='bar.y')[0]
958 src = tgt.sources[0]
959 assert tgt.builder.target_scanner != scanner, tgt.builder.target_scanner
960 assert not tgt.builder.source_scanner, tgt.builder.source_scanner
961 assert tgt.get_source_scanner(bar_y), tgt.get_source_scanner(bar_y)
962 assert str(tgt.get_source_scanner(bar_y)) == 'EnvTestScanner', tgt.get_source_scanner(bar_y)
963 assert not src.has_builder(), src.has_builder()
964 s = src.get_source_scanner(bar_y)
965 assert isinstance(s, SCons.Util.Null), repr(s)
967 # Can't simply specify the scanner as a builder argument; it's
968 # global to all invocations of this builder.
969 tgt = builder(env3, target='foo3.x', source='bar.y', source_scanner = scanner)[0]
970 src = tgt.sources[0]
971 assert tgt.builder.target_scanner != scanner, tgt.builder.target_scanner
972 assert not tgt.builder.source_scanner, tgt.builder.source_scanner
973 assert tgt.get_source_scanner(bar_y), tgt.get_source_scanner(bar_y)
974 assert str(tgt.get_source_scanner(bar_y)) == 'EnvTestScanner', tgt.get_source_scanner(bar_y)
975 assert not src.has_builder(), src.has_builder()
976 s = src.get_source_scanner(bar_y)
977 assert isinstance(s, SCons.Util.Null), s
979 # Now use a builder that actually has scanners and ensure that
980 # the target is set accordingly (using the specified scanner
981 # instead of the Environment's scanner)
982 builder = SCons.Builder.Builder(action='action',
983 source_scanner=scanner,
984 target_scanner=scanner)
985 tgt = builder(env3, target='foo4.x', source='bar.y')[0]
986 src = tgt.sources[0]
987 assert tgt.builder.target_scanner == scanner, tgt.builder.target_scanner
988 assert tgt.builder.source_scanner, tgt.builder.source_scanner
989 assert tgt.builder.source_scanner == scanner, tgt.builder.source_scanner
990 assert str(tgt.builder.source_scanner) == 'TestScanner', str(tgt.builder.source_scanner)
991 assert tgt.get_source_scanner(bar_y), tgt.get_source_scanner(bar_y)
992 assert tgt.get_source_scanner(bar_y) == scanner, tgt.get_source_scanner(bar_y)
993 assert str(tgt.get_source_scanner(bar_y)) == 'TestScanner', tgt.get_source_scanner(bar_y)
994 assert not src.has_builder(), src.has_builder()
995 s = src.get_source_scanner(bar_y)
996 assert isinstance(s, SCons.Util.Null), s
1000 def test_Builder_API(self) -> None:
1001 """Test Builder interface.
1003 Some of this is tested elsewhere in this file, but this is a
1004 quick collection of common operations on builders with various
1005 forms of component specifications."""
1007 builder = SCons.Builder.Builder()
1008 env = Environment(BUILDERS={'Bld':builder})
1010 r = builder.get_name(env)
1011 assert r == 'Bld', r
1012 r = builder.get_prefix(env)
1013 assert r == '', r
1014 r = builder.get_suffix(env)
1015 assert r == '', r
1016 r = builder.get_src_suffix(env)
1017 assert r == '', r
1018 r = builder.src_suffixes(env)
1019 assert r == [], r
1021 # src_suffix can be a single string or a list of strings
1022 # src_suffixes() caches its return value, so we use a new
1023 # Builder each time we do any of these tests
1025 bld = SCons.Builder.Builder()
1026 env = Environment(BUILDERS={'Bld':bld})
1028 bld.set_src_suffix('.foo')
1029 r = bld.get_src_suffix(env)
1030 assert r == '.foo', r
1031 r = bld.src_suffixes(env)
1032 assert r == ['.foo'], r
1034 bld = SCons.Builder.Builder()
1035 env = Environment(BUILDERS={'Bld':bld})
1037 bld.set_src_suffix(['.foo', '.bar'])
1038 r = bld.get_src_suffix(env)
1039 assert r == '.foo', r
1040 r = bld.src_suffixes(env)
1041 assert r == ['.foo', '.bar'], r
1043 bld = SCons.Builder.Builder()
1044 env = Environment(BUILDERS={'Bld':bld})
1046 bld.set_src_suffix(['.bar', '.foo'])
1047 r = bld.get_src_suffix(env)
1048 assert r == '.bar', r
1049 r = sorted(bld.src_suffixes(env))
1050 assert r == ['.bar', '.foo'], r
1052 # adjust_suffix normalizes the suffix, adding a `.' if needed
1054 r = builder.adjust_suffix('.foo')
1055 assert r == '.foo', r
1056 r = builder.adjust_suffix('_foo')
1057 assert r == '_foo', r
1058 r = builder.adjust_suffix('$foo')
1059 assert r == '$foo', r
1060 r = builder.adjust_suffix('foo')
1061 assert r == '.foo', r
1062 r = builder.adjust_suffix('f._$oo')
1063 assert r == '.f._$oo', r
1065 # prefix and suffix can be one of:
1066 # 1. a string (adjusted and env variables substituted),
1067 # 2. a function (passed (env,sources), returns suffix string)
1068 # 3. a dict of src_suffix:suffix settings, key==None is
1069 # default suffix (special case of #2, so adjust_suffix
1070 # not applied)
1072 builder = SCons.Builder.Builder(prefix='lib', suffix='foo')
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 == 'lib', r
1079 r = builder.get_suffix(env)
1080 assert r == '.foo', r
1082 mkpref = lambda env,sources: 'Lib'
1083 mksuff = lambda env,sources: '.Foo'
1084 builder = SCons.Builder.Builder(prefix=mkpref, suffix=mksuff)
1086 env = Environment(BUILDERS={'Bld':builder})
1087 r = builder.get_name(env)
1088 assert r == 'Bld', r
1089 r = builder.get_prefix(env)
1090 assert r == 'Lib', r
1091 r = builder.get_suffix(env)
1092 assert r == '.Foo', r
1094 builder = SCons.Builder.Builder(prefix='$PREF', suffix='$SUFF')
1096 env = Environment(BUILDERS={'Bld':builder},PREF="LIB",SUFF=".FOO")
1097 r = builder.get_name(env)
1098 assert r == 'Bld', r
1099 r = builder.get_prefix(env)
1100 assert r == 'LIB', r
1101 r = builder.get_suffix(env)
1102 assert r == '.FOO', r
1104 builder = SCons.Builder.Builder(prefix={None:'A_',
1105 '.C':'E_'},
1106 suffix={None:'.B',
1107 '.C':'.D'})
1109 env = Environment(BUILDERS={'Bld':builder})
1110 r = builder.get_name(env)
1111 assert r == 'Bld', r
1112 r = builder.get_prefix(env)
1113 assert r == 'A_', r
1114 r = builder.get_suffix(env)
1115 assert r == '.B', r
1116 r = builder.get_prefix(env, [MyNode('X.C')])
1117 assert r == 'E_', r
1118 r = builder.get_suffix(env, [MyNode('X.C')])
1119 assert r == '.D', r
1121 builder = SCons.Builder.Builder(prefix='A_', suffix={}, action={})
1122 env = Environment(BUILDERS={'Bld':builder})
1124 r = builder.get_name(env)
1125 assert r == 'Bld', r
1126 r = builder.get_prefix(env)
1127 assert r == 'A_', r
1128 r = builder.get_suffix(env)
1129 assert r is None, r
1130 r = builder.get_src_suffix(env)
1131 assert r == '', r
1132 r = builder.src_suffixes(env)
1133 assert r == [], r
1135 # Builder actions can be a string, a list, or a dictionary
1136 # whose keys are the source suffix. The add_action()
1137 # specifies a new source suffix/action binding.
1139 builder = SCons.Builder.Builder(prefix='A_', suffix={}, action={})
1140 env = Environment(BUILDERS={'Bld':builder})
1141 builder.add_action('.src_sfx1', 'FOO')
1143 r = builder.get_name(env)
1144 assert r == 'Bld', r
1145 r = builder.get_prefix(env)
1146 assert r == 'A_', r
1147 r = builder.get_suffix(env)
1148 assert r is None, r
1149 r = builder.get_suffix(env, [MyNode('X.src_sfx1')])
1150 assert r is None, r
1151 r = builder.get_src_suffix(env)
1152 assert r == '.src_sfx1', r
1153 r = builder.src_suffixes(env)
1154 assert r == ['.src_sfx1'], r
1156 builder = SCons.Builder.Builder(prefix='A_', suffix={}, action={})
1157 env = Environment(BUILDERS={'Bld':builder})
1158 builder.add_action('.src_sfx1', 'FOO')
1159 builder.add_action('.src_sfx2', 'BAR')
1161 r = builder.get_name(env)
1162 assert r == 'Bld', r
1163 r = builder.get_prefix(env)
1164 assert r == 'A_', r
1165 r = builder.get_suffix(env)
1166 assert r is None, r
1167 r = builder.get_src_suffix(env)
1168 assert r == '.src_sfx1', r
1169 r = sorted(builder.src_suffixes(env))
1170 assert r == ['.src_sfx1', '.src_sfx2'], r
1173 def test_Builder_Args(self) -> None:
1174 """Testing passing extra args to a builder."""
1175 def buildFunc(target, source, env, s=self) -> None:
1176 s.foo=env['foo']
1177 s.bar=env['bar']
1178 assert env['CC'] == 'mycc'
1180 env=Environment(CC='cc')
1182 builder = SCons.Builder.Builder(action=buildFunc)
1183 tgt = builder(env, target='foo', source='bar', foo=1, bar=2, CC='mycc')[0]
1184 tgt.build()
1185 assert self.foo == 1, self.foo
1186 assert self.bar == 2, self.bar
1188 def test_emitter(self) -> None:
1189 """Test emitter functions."""
1190 def emit(target, source, env):
1191 foo = env.get('foo', 0)
1192 bar = env.get('bar', 0)
1193 for t in target:
1194 assert isinstance(t, MyNode)
1195 assert t.has_builder()
1196 for s in source:
1197 assert isinstance(s, MyNode)
1198 if foo:
1199 target.append("bar%d"%foo)
1200 if bar:
1201 source.append("baz")
1202 return ( target, source )
1204 env = Environment()
1205 builder = SCons.Builder.Builder(action='foo',
1206 emitter=emit,
1207 target_factory=MyNode,
1208 source_factory=MyNode)
1209 tgt = builder(env, target='foo2', source='bar')[0]
1210 assert str(tgt) == 'foo2', str(tgt)
1211 assert str(tgt.sources[0]) == 'bar', str(tgt.sources[0])
1213 tgt = builder(env, target='foo3', source='bar', foo=1)
1214 assert len(tgt) == 2, len(tgt)
1215 assert 'foo3' in list(map(str, tgt)), list(map(str, tgt))
1216 assert 'bar1' in list(map(str, tgt)), list(map(str, tgt))
1218 tgt = builder(env, target='foo4', source='bar', bar=1)[0]
1219 assert str(tgt) == 'foo4', str(tgt)
1220 assert len(tgt.sources) == 2, len(tgt.sources)
1221 assert 'baz' in list(map(str, tgt.sources)), list(map(str, tgt.sources))
1222 assert 'bar' in list(map(str, tgt.sources)), list(map(str, tgt.sources))
1224 env2=Environment(FOO=emit)
1225 builder2=SCons.Builder.Builder(action='foo',
1226 emitter="$FOO",
1227 target_factory=MyNode,
1228 source_factory=MyNode)
1230 builder2a=SCons.Builder.Builder(action='foo',
1231 emitter="$FOO",
1232 target_factory=MyNode,
1233 source_factory=MyNode)
1235 assert builder2 == builder2a, repr(builder2.__dict__) + "\n" + repr(builder2a.__dict__)
1237 tgt = builder2(env2, target='foo5', source='bar')[0]
1238 assert str(tgt) == 'foo5', str(tgt)
1239 assert str(tgt.sources[0]) == 'bar', str(tgt.sources[0])
1241 tgt = builder2(env2, target='foo6', source='bar', foo=2)
1242 assert len(tgt) == 2, len(tgt)
1243 assert 'foo6' in list(map(str, tgt)), list(map(str, tgt))
1244 assert 'bar2' in list(map(str, tgt)), list(map(str, tgt))
1246 tgt = builder2(env2, target='foo7', source='bar', bar=1)[0]
1247 assert str(tgt) == 'foo7', str(tgt)
1248 assert len(tgt.sources) == 2, len(tgt.sources)
1249 assert 'baz' in list(map(str, tgt.sources)), list(map(str, tgt.sources))
1250 assert 'bar' in list(map(str, tgt.sources)), list(map(str, tgt.sources))
1252 def test_emitter_preserve_builder(self) -> None:
1253 """Test an emitter not overwriting a newly-set builder"""
1254 env = Environment()
1256 new_builder = SCons.Builder.Builder(action='new')
1257 node = MyNode('foo8')
1258 new_node = MyNode('foo8.new')
1260 def emit(target, source, env, nb=new_builder, nn=new_node):
1261 for t in target:
1262 t.builder = nb
1263 return [nn], source
1265 builder=SCons.Builder.Builder(action='foo',
1266 emitter=emit,
1267 target_factory=MyNode,
1268 source_factory=MyNode)
1269 tgt = builder(env, target=node, source='bar')[0]
1270 assert tgt is new_node, tgt
1271 assert tgt.builder is builder, tgt.builder
1272 assert node.builder is new_builder, node.builder
1274 def test_emitter_suffix_map(self) -> None:
1275 """Test mapping file suffixes to emitter functions"""
1276 env = Environment()
1278 def emit4a(target, source, env):
1279 source = list(map(str, source))
1280 target = ['emit4a-' + x[:-3] for x in source]
1281 return (target, source)
1282 def emit4b(target, source, env):
1283 source = list(map(str, source))
1284 target = ['emit4b-' + x[:-3] for x in source]
1285 return (target, source)
1287 builder = SCons.Builder.Builder(action='foo',
1288 emitter={'.4a':emit4a,
1289 '.4b':emit4b},
1290 target_factory=MyNode,
1291 source_factory=MyNode)
1292 tgt = builder(env, None, source='aaa.4a')[0]
1293 assert str(tgt) == 'emit4a-aaa', str(tgt)
1294 tgt = builder(env, None, source='bbb.4b')[0]
1295 assert str(tgt) == 'emit4b-bbb', str(tgt)
1296 tgt = builder(env, None, source='ccc.4c')[0]
1297 assert str(tgt) == 'ccc', str(tgt)
1299 def emit4c(target, source, env):
1300 source = list(map(str, source))
1301 target = ['emit4c-' + x[:-3] for x in source]
1302 return (target, source)
1304 builder.add_emitter('.4c', emit4c)
1305 tgt = builder(env, None, source='ccc.4c')[0]
1306 assert str(tgt) == 'emit4c-ccc', str(tgt)
1308 def test_emitter_function_list(self) -> None:
1309 """Test lists of emitter functions"""
1310 env = Environment()
1312 def emit1a(target, source, env):
1313 source = list(map(str, source))
1314 target = target + ['emit1a-' + x[:-2] for x in source]
1315 return (target, source)
1316 def emit1b(target, source, env):
1317 source = list(map(str, source))
1318 target = target + ['emit1b-' + x[:-2] for x in source]
1319 return (target, source)
1320 builder1 = SCons.Builder.Builder(action='foo',
1321 emitter=[emit1a, emit1b],
1322 node_factory=MyNode)
1324 tgts = builder1(env, target='target-1', source='aaa.1')
1325 tgts = list(map(str, tgts))
1326 assert tgts == ['target-1', 'emit1a-aaa', 'emit1b-aaa'], tgts
1328 # Test a list of emitter functions through the environment.
1329 def emit2a(target, source, env):
1330 source = list(map(str, source))
1331 target = target + ['emit2a-' + x[:-2] for x in source]
1332 return (target, source)
1333 def emit2b(target, source, env):
1334 source = list(map(str, source))
1335 target = target + ['emit2b-' + x[:-2] for x in source]
1336 return (target, source)
1337 builder2 = SCons.Builder.Builder(action='foo',
1338 emitter='$EMITTERLIST',
1339 node_factory=MyNode)
1341 env = Environment(EMITTERLIST = [emit2a, emit2b])
1343 tgts = builder2(env, target='target-2', source='aaa.2')
1344 tgts = list(map(str, tgts))
1345 assert tgts == ['target-2', 'emit2a-aaa', 'emit2b-aaa'], tgts
1347 def test_emitter_TARGET_SOURCE(self) -> None:
1348 """Test use of $TARGET and $SOURCE in emitter results"""
1350 env = SCons.Environment.Environment()
1352 def emit(target, source, env):
1353 return (target + ['${SOURCE}.s1', '${TARGET}.t1'],
1354 source + ['${TARGET}.t2', '${SOURCE}.s2'])
1356 builder = SCons.Builder.Builder(action='foo',
1357 emitter = emit,
1358 node_factory = MyNode)
1360 targets = builder(env, target = 'TTT', source ='SSS')
1361 sources = targets[0].sources
1362 targets = list(map(str, targets))
1363 sources = list(map(str, sources))
1364 assert targets == ['TTT', 'SSS.s1', 'TTT.t1'], targets
1365 assert sources == ['SSS', 'TTT.t2', 'SSS.s2'], targets
1367 def test_no_target(self) -> None:
1368 """Test deducing the target from the source."""
1370 env = Environment()
1371 b = SCons.Builder.Builder(action='foo', suffix='.o')
1373 tgt = b(env, None, 'aaa')[0]
1374 assert str(tgt) == 'aaa.o', str(tgt)
1375 assert len(tgt.sources) == 1, list(map(str, tgt.sources))
1376 assert str(tgt.sources[0]) == 'aaa', list(map(str, tgt.sources))
1378 tgt = b(env, None, 'bbb.c')[0]
1379 assert str(tgt) == 'bbb.o', str(tgt)
1380 assert len(tgt.sources) == 1, list(map(str, tgt.sources))
1381 assert str(tgt.sources[0]) == 'bbb.c', list(map(str, tgt.sources))
1383 tgt = b(env, None, 'ccc.x.c')[0]
1384 assert str(tgt) == 'ccc.x.o', str(tgt)
1385 assert len(tgt.sources) == 1, list(map(str, tgt.sources))
1386 assert str(tgt.sources[0]) == 'ccc.x.c', list(map(str, tgt.sources))
1388 tgt = b(env, None, ['d0.c', 'd1.c'])[0]
1389 assert str(tgt) == 'd0.o', str(tgt)
1390 assert len(tgt.sources) == 2, list(map(str, tgt.sources))
1391 assert str(tgt.sources[0]) == 'd0.c', list(map(str, tgt.sources))
1392 assert str(tgt.sources[1]) == 'd1.c', list(map(str, tgt.sources))
1394 tgt = b(env, target = None, source='eee')[0]
1395 assert str(tgt) == 'eee.o', str(tgt)
1396 assert len(tgt.sources) == 1, list(map(str, tgt.sources))
1397 assert str(tgt.sources[0]) == 'eee', list(map(str, tgt.sources))
1399 tgt = b(env, target = None, source='fff.c')[0]
1400 assert str(tgt) == 'fff.o', str(tgt)
1401 assert len(tgt.sources) == 1, list(map(str, tgt.sources))
1402 assert str(tgt.sources[0]) == 'fff.c', list(map(str, tgt.sources))
1404 tgt = b(env, target = None, source='ggg.x.c')[0]
1405 assert str(tgt) == 'ggg.x.o', str(tgt)
1406 assert len(tgt.sources) == 1, list(map(str, tgt.sources))
1407 assert str(tgt.sources[0]) == 'ggg.x.c', list(map(str, tgt.sources))
1409 tgt = b(env, target = None, source=['h0.c', 'h1.c'])[0]
1410 assert str(tgt) == 'h0.o', str(tgt)
1411 assert len(tgt.sources) == 2, list(map(str, tgt.sources))
1412 assert str(tgt.sources[0]) == 'h0.c', list(map(str, tgt.sources))
1413 assert str(tgt.sources[1]) == 'h1.c', list(map(str, tgt.sources))
1415 w = b(env, target='i0.w', source=['i0.x'])[0]
1416 y = b(env, target='i1.y', source=['i1.z'])[0]
1417 tgt = b(env, None, source=[w, y])[0]
1418 assert str(tgt) == 'i0.o', str(tgt)
1419 assert len(tgt.sources) == 2, list(map(str, tgt.sources))
1420 assert str(tgt.sources[0]) == 'i0.w', list(map(str, tgt.sources))
1421 assert str(tgt.sources[1]) == 'i1.y', list(map(str, tgt.sources))
1423 def test_get_name(self) -> None:
1424 """Test getting name of builder.
1426 Each type of builder should return its environment-specific
1427 name when queried appropriately. """
1429 b1 = SCons.Builder.Builder(action='foo', suffix='.o')
1430 b2 = SCons.Builder.Builder(action='foo', suffix='.c')
1431 b3 = SCons.Builder.Builder(action='bar', src_suffix = '.foo',
1432 src_builder = b1)
1433 b4 = SCons.Builder.Builder(action={})
1434 b5 = SCons.Builder.Builder(action='foo', name='builder5')
1435 b6 = SCons.Builder.Builder(action='foo')
1436 assert isinstance(b4, SCons.Builder.CompositeBuilder)
1437 assert isinstance(b4.action, SCons.Action.CommandGeneratorAction)
1439 env = Environment(BUILDERS={'bldr1': b1,
1440 'bldr2': b2,
1441 'bldr3': b3,
1442 'bldr4': b4})
1443 env2 = Environment(BUILDERS={'B1': b1,
1444 'B2': b2,
1445 'B3': b3,
1446 'B4': b4})
1447 # With no name, get_name will return the class. Allow
1448 # for caching...
1449 b6_names = [
1450 'SCons.Builder.BuilderBase',
1451 "<class 'SCons.Builder.BuilderBase'>",
1452 'SCons.Memoize.BuilderBase',
1453 "<class 'SCons.Memoize.BuilderBase'>",
1456 assert b1.get_name(env) == 'bldr1', b1.get_name(env)
1457 assert b2.get_name(env) == 'bldr2', b2.get_name(env)
1458 assert b3.get_name(env) == 'bldr3', b3.get_name(env)
1459 assert b4.get_name(env) == 'bldr4', b4.get_name(env)
1460 assert b5.get_name(env) == 'builder5', b5.get_name(env)
1461 assert b6.get_name(env) in b6_names, b6.get_name(env)
1463 assert b1.get_name(env2) == 'B1', b1.get_name(env2)
1464 assert b2.get_name(env2) == 'B2', b2.get_name(env2)
1465 assert b3.get_name(env2) == 'B3', b3.get_name(env2)
1466 assert b4.get_name(env2) == 'B4', b4.get_name(env2)
1467 assert b5.get_name(env2) == 'builder5', b5.get_name(env2)
1468 assert b6.get_name(env2) in b6_names, b6.get_name(env2)
1470 assert b5.get_name(None) == 'builder5', b5.get_name(None)
1471 assert b6.get_name(None) in b6_names, b6.get_name(None)
1473 # This test worked before adding batch builders, but we must now
1474 # be able to disambiguate a CompositeAction into a more specific
1475 # action based on file suffix at call time. Leave this commented
1476 # out (for now) in case this reflects a real-world use case that
1477 # we must accomodate and we want to resurrect this test.
1478 #tgt = b4(env, target = 'moo', source='cow')
1479 #assert tgt[0].builder.get_name(env) == 'bldr4'
1481 class CompositeBuilderTestCase(unittest.TestCase):
1483 def setUp(self) -> None:
1484 def func_action(target, source, env) -> int:
1485 return 0
1487 builder = SCons.Builder.Builder(action={ '.foo' : func_action,
1488 '.bar' : func_action})
1490 self.func_action = func_action
1491 self.builder = builder
1493 def test___init__(self) -> None:
1494 """Test CompositeBuilder creation"""
1495 env = Environment()
1496 builder = SCons.Builder.Builder(action={})
1498 tgt = builder(env, source=[])
1499 assert tgt == [], tgt
1501 assert isinstance(builder, SCons.Builder.CompositeBuilder)
1502 assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
1504 def test_target_action(self) -> None:
1505 """Test CompositeBuilder setting of target builder actions"""
1506 env = Environment()
1507 builder = self.builder
1509 tgt = builder(env, target='test1', source='test1.foo')[0]
1510 assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
1511 assert tgt.builder.action is builder.action
1513 tgt = builder(env, target='test2', source='test1.bar')[0]
1514 assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
1515 assert tgt.builder.action is builder.action
1517 def test_multiple_suffix_error(self) -> None:
1518 """Test the CompositeBuilder multiple-source-suffix error"""
1519 env = Environment()
1520 builder = self.builder
1522 flag = 0
1523 try:
1524 builder(env, target='test3', source=['test2.bar', 'test1.foo'])[0]
1525 except SCons.Errors.UserError as e:
1526 flag = 1
1527 err = e
1528 assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
1529 expect = "While building `['test3']' from `test1.foo': Cannot build multiple sources with different extensions: .bar, .foo"
1530 assert str(err) == expect, err
1532 def test_source_ext_match(self) -> None:
1533 """Test the CompositeBuilder source_ext_match argument"""
1534 env = Environment()
1535 func_action = self.func_action
1536 builder = SCons.Builder.Builder(action={ '.foo' : func_action,
1537 '.bar' : func_action},
1538 source_ext_match = None)
1540 tgt = builder(env, target='test3', source=['test2.bar', 'test1.foo'])[0]
1541 tgt.build()
1543 def test_suffix_variable(self) -> None:
1544 """Test CompositeBuilder defining action suffixes through a variable"""
1545 env = Environment(BAR_SUFFIX = '.BAR2', FOO_SUFFIX = '.FOO2')
1546 func_action = self.func_action
1547 builder = SCons.Builder.Builder(action={ '.foo' : func_action,
1548 '.bar' : func_action,
1549 '$BAR_SUFFIX' : func_action,
1550 '$FOO_SUFFIX' : func_action })
1552 tgt = builder(env, target='test4', source=['test4.BAR2'])[0]
1553 assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
1554 try:
1555 tgt.build()
1556 flag = 1
1557 except SCons.Errors.UserError as e:
1558 print(e)
1559 flag = 0
1560 assert flag, "It should be possible to define actions in composite builders using variables."
1561 env['FOO_SUFFIX'] = '.BAR2'
1562 builder.add_action('$NEW_SUFFIX', func_action)
1563 flag = 0
1564 try:
1565 builder(env, target='test5', source=['test5.BAR2'])[0]
1566 except SCons.Errors.UserError:
1567 flag = 1
1568 assert flag, "UserError should be thrown when we call a builder with ambigous suffixes."
1570 def test_src_builder(self) -> None:
1571 """Test CompositeBuilder's use of a src_builder"""
1572 env = Environment()
1574 foo_bld = SCons.Builder.Builder(action = 'a-foo',
1575 src_suffix = '.ina',
1576 suffix = '.foo')
1577 assert isinstance(foo_bld, SCons.Builder.BuilderBase)
1578 builder = SCons.Builder.Builder(action = { '.foo' : 'foo',
1579 '.bar' : 'bar' },
1580 src_builder = foo_bld)
1581 assert isinstance(builder, SCons.Builder.CompositeBuilder)
1582 assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
1584 tgt = builder(env, target='t1', source='t1a.ina t1b.ina')[0]
1585 assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
1587 tgt = builder(env, target='t2', source='t2a.foo t2b.ina')[0]
1588 assert isinstance(tgt.builder, SCons.Builder.BuilderBase), tgt.builder.__dict__
1590 bar_bld = SCons.Builder.Builder(action = 'a-bar',
1591 src_suffix = '.inb',
1592 suffix = '.bar')
1593 assert isinstance(bar_bld, SCons.Builder.BuilderBase)
1594 builder = SCons.Builder.Builder(action = { '.foo' : 'foo'},
1595 src_builder = [foo_bld, bar_bld])
1596 assert isinstance(builder, SCons.Builder.CompositeBuilder)
1597 assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
1599 builder.add_action('.bar', 'bar')
1601 tgt = builder(env, target='t3-foo', source='t3a.foo t3b.ina')[0]
1602 assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
1604 tgt = builder(env, target='t3-bar', source='t3a.bar t3b.inb')[0]
1605 assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
1607 flag = 0
1608 try:
1609 builder(env, target='t5', source=['test5a.foo', 'test5b.inb'])[0]
1610 except SCons.Errors.UserError as e:
1611 flag = 1
1612 err = e
1613 assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
1614 expect = "While building `['t5']' from `test5b.bar': Cannot build multiple sources with different extensions: .foo, .bar"
1615 assert str(err) == expect, err
1617 flag = 0
1618 try:
1619 builder(env, target='t6', source=['test6a.bar', 'test6b.ina'])[0]
1620 except SCons.Errors.UserError as e:
1621 flag = 1
1622 err = e
1623 assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
1624 expect = "While building `['t6']' from `test6b.foo': Cannot build multiple sources with different extensions: .bar, .foo"
1625 assert str(err) == expect, err
1627 flag = 0
1628 try:
1629 builder(env, target='t4', source=['test4a.ina', 'test4b.inb'])[0]
1630 except SCons.Errors.UserError as e:
1631 flag = 1
1632 err = e
1633 assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
1634 expect = "While building `['t4']' from `test4b.bar': Cannot build multiple sources with different extensions: .foo, .bar"
1635 assert str(err) == expect, err
1637 flag = 0
1638 try:
1639 builder(env, target='t7', source=[env.fs.File('test7')])[0]
1640 except SCons.Errors.UserError as e:
1641 flag = 1
1642 err = e
1643 assert flag, "UserError should be thrown when we call a builder with files of different suffixes."
1644 expect = "While building `['t7']': Cannot deduce file extension from source files: ['test7']"
1645 assert str(err) == expect, err
1647 flag = 0
1648 try:
1649 builder(env, target='t8', source=['test8.unknown'])[0]
1650 except SCons.Errors.UserError as e:
1651 flag = 1
1652 err = e
1653 assert flag, "UserError should be thrown when we call a builder target with an unknown suffix."
1654 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']."
1655 assert str(err) == expect, err
1657 if __name__ == "__main__":
1658 unittest.main()
1660 # Local Variables:
1661 # tab-width:4
1662 # indent-tabs-mode:nil
1663 # End:
1664 # vim: set expandtab tabstop=4 shiftwidth=4: