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.
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.
33 from collections
import UserList
44 import SCons
.Environment
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")
69 scons_env
= SCons
.Environment
.Environment()
71 env_arg2nodes_called
= None
74 def __init__(self
, **kw
) -> None:
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():
81 global env_arg2nodes_called
82 env_arg2nodes_called
= None
84 self
.fs
= SCons
.Node
.FS
.FS()
86 if not SCons
.Util
.is_String(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
,
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
,
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
):
105 if SCons
.Util
.is_String(a
):
106 a
= factory(self
.subst(a
))
109 def get_factory(self
, factory
):
110 return factory
or self
.fs
.File
111 def get_scanner(self
, ext
):
113 def Dictionary(self
):
115 def autogenerate(self
, dir: str=''):
117 def __setitem__(self
, item
, var
) -> None:
119 def __getitem__(self
, item
):
121 def __contains__(self
, key
) -> bool:
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
132 def _update(self
, dict) -> None:
135 return list(self
.d
.items())
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]
144 def __eq__(self
, other
):
145 return self
.scanner
== other
.scanner
or self
.d
== other
.d
148 def __init__(self
, action
) -> None:
150 def __call__(self
, *args
, **kw
) -> None:
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:
160 self
.is_explicit
= None
162 def get_suffix(self
):
163 return os
.path
.splitext(self
.name
)[1]
164 def disambiguate(self
):
166 def __str__(self
) -> str:
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:
178 def add_source(self
, source
) -> None:
179 self
.sources
.extend(source
)
180 def scanner_key(self
):
182 def is_derived(self
) -> bool:
183 return self
.has_builder()
184 def generate_build_env(self
, 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
:
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']
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")
216 except SCons
.Errors
.InternalError
:
218 assert exc_caught
, "did not catch expected InternalError exception"
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
235 except SCons
.Errors
.InternalError
:
237 assert exc_caught
, "did not catch expected InternalError exception"
239 def test__call__(self
):
240 """Test calling a builder to establish source dependencies
243 builder
= SCons
.Builder
.Builder(action
="foo",
244 target_factory
=MyNode
,
245 source_factory
=MyNode
)
247 tgt
= builder(env
, source
=[])
248 assert tgt
== [], tgt
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')
266 return str(list(map(str, l
)))
270 nnn1
= MyNode("nnn1")
271 nnn2
= MyNode("nnn2")
272 tlist
= builder(env
, target
= [nnn1
, nnn2
], source
= [])
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')
280 assert s
== "['n3']", s
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'])
289 assert s
== "['n4 n5']", s
290 l
= list(map(str, tlist
))
291 assert l
== ['n4 n5'], l
293 assert target
.name
== 'n4 n5'
294 assert target
.sources
[0].name
== 'n6 n7'
296 tlist
= builder(env
, target
= ['n8 n9'], source
= 'n10 n11')
298 assert s
== "['n8 n9']", s
299 l
= list(map(str, tlist
))
300 assert l
== ['n8 n9'], l
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')
310 #be = target.get_build_env()
311 #assert be['VAR'] == 'foo', be['VAR']
313 n20
= MyNode_without_target_from_source('n20')
316 target
= builder(env
, None, source
=n20
)
317 except SCons
.Errors
.UserError
as e
:
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
,
326 target
= builder(env
, None, source
='n21')[0]
327 assert target
.name
== 'p-n21.s', target
329 builder
= SCons
.Builder
.Builder(misspelled_action
="foo",
332 builder(env
, target
= 'n22', source
= 'n22')
333 except SCons
.Errors
.UserError
as e
:
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
348 builder
= SCons
.Builder
.Builder(action
="foo")
350 save_warn
= SCons
.Warnings
.warn
352 def my_warn(exception
, warning
, warned
=warned
) -> None:
353 warned
.append(warning
)
354 SCons
.Warnings
.warn
= my_warn
357 target
= builder(env
, 'mistaken1', sources
='mistaken1.c')
358 assert warned
== ["Did you mean to use `source' instead of `sources'?"], warned
361 target
= builder(env
, 'mistaken2', targets
='mistaken2.c')
362 assert warned
== ["Did you mean to use `target' instead of `targets'?"], 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
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"
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:
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')
404 b3
= SCons
.Builder
.Builder(src_suffix
= '.x')
408 def test_target_factory(self
) -> None:
409 """Test a Builder that creates target nodes of a specified class
413 def FooFactory(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
425 def FooFactory(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()"
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()"
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.
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-',
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
)
528 s
= b
.get_suffix(env
)
529 assert s
== '.foo', s
531 b
.set_suffix('$XSUFFIX')
532 s
= b
.get_suffix(env
)
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"""
572 b6
= SCons
.Builder
.Builder(action
= '',
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',
581 b8
= SCons
.Builder
.Builder(action
= '',
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'},
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
= '',
603 b2
= SCons
.Builder
.Builder(action
= '',
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.
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',
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
670 r
= builder
._adjustixes
('file', 'pre-', '-suf')
671 self
.assertEqual(r
, ['pre-file-suf'])
673 # path with a suffix should not have one added
675 r
= builder
._adjustixes
('file.o', 'pre-', '-suf')
676 self
.assertEqual(r
, ['pre-file.o'])
678 # unless ensure_suffix is True
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
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
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"):
702 if len(source
) == 1 and len(target
) == 1:
703 env
['CNT'][0] = env
['CNT'][0] + 1
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')
715 tgt
= builder(env
, target
=outfiles
[0], source
=infiles
[0])[0]
717 t
= os
.path
.normcase(test
.workpath('0.out'))
718 assert os
.path
.normcase(s
) == t
, s
721 assert env
['CNT'][0] == 1, env
['CNT'][0]
722 tgt
= builder(env
, outfiles
[1], infiles
[1])[0]
724 t
= os
.path
.normcase(test
.workpath('1.out'))
725 assert os
.path
.normcase(s
) == t
, s
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()
737 assert env
['CNT'][0] == 4
739 tgt
= builder(env
, outfiles
[4], infiles
[4:6])
740 except SCons
.Errors
.UserError
:
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
:
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:
757 with
open(str(t
), 'w') as f
:
758 f
.write("function2\n")
760 if t
not in list(map(str, target
)):
761 with
open(t
, 'w') as f
:
762 f
.write("function2\n")
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
)
776 except SCons
.Errors
.BuildError
:
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:
788 with
open(str(t
), 'w') as f
:
789 f
.write("function3\n")
791 if t
not in list(map(str, target
)):
792 with
open(t
, 'w') as f
:
793 f
.write("function3\n")
796 builder
= SCons
.Builder
.Builder(action
= function3
)
797 tgts
= builder(env
, target
= [sub1_out
, sub2_out
], source
= infile
)
802 except SCons
.Errors
.BuildError
:
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
816 builder1
= SCons
.Builder
.Builder(action
='foo',
819 builder2
= SCons
.Builder
.Builder(action
=MyAction('act'),
820 src_builder
= builder1
,
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]
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]
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',
849 builder5
= SCons
.Builder
.Builder(action
=MyAction('act'),
850 src_builder
=builder4
,
853 builder6
= SCons
.Builder
.Builder(action
=MyAction('act'),
854 src_builder
=builder5
,
857 tgt
= builder6(env
, 'test', 'test.i')[0]
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."""
872 tscan
= TestScanner()
873 sscan
= TestScanner()
875 builder
= SCons
.Builder
.Builder(target_scanner
=tscan
,
876 source_scanner
=sscan
,
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',
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."""
898 def func(self
) -> None:
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')
910 def test_src_scanner(self
) -> None:
911 """Testing ability to set a source file scanner through a builder."""
913 def key(self
, env
) -> str:
914 return 'TestScannerkey'
915 def instance(self
, env
):
917 def select(self
, node
):
920 def __str__(self
) -> str:
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')
930 tgt
= builder(env1
, target
='foo1.x', source
='bar.y')[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:
944 def instance(self
, env
):
946 name
= 'EnvTestScanner'
947 def __str__(self
) -> str:
949 def select(self
, node
):
951 def path(self
, env
, dir=None):
953 def __call__(self
, node
, env
, path
):
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]
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]
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]
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
)
1014 r
= builder
.get_suffix(env
)
1016 r
= builder
.get_src_suffix(env
)
1018 r
= builder
.src_suffixes(env
)
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
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_',
1109 env
= Environment(BUILDERS
={'Bld':builder
})
1110 r
= builder
.get_name(env
)
1111 assert r
== 'Bld', r
1112 r
= builder
.get_prefix(env
)
1114 r
= builder
.get_suffix(env
)
1116 r
= builder
.get_prefix(env
, [MyNode('X.C')])
1118 r
= builder
.get_suffix(env
, [MyNode('X.C')])
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
)
1128 r
= builder
.get_suffix(env
)
1130 r
= builder
.get_src_suffix(env
)
1132 r
= builder
.src_suffixes(env
)
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
)
1147 r
= builder
.get_suffix(env
)
1149 r
= builder
.get_suffix(env
, [MyNode('X.src_sfx1')])
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
)
1165 r
= builder
.get_suffix(env
)
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:
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]
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)
1194 assert isinstance(t
, MyNode
)
1195 assert t
.has_builder()
1197 assert isinstance(s
, MyNode
)
1199 target
.append("bar%d"%foo)
1201 source
.append("baz")
1202 return ( target
, source
)
1205 builder
= SCons
.Builder
.Builder(action
='foo',
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',
1227 target_factory
=MyNode
,
1228 source_factory
=MyNode
)
1230 builder2a
=SCons
.Builder
.Builder(action
='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"""
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
):
1265 builder
=SCons
.Builder
.Builder(action
='foo',
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"""
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
,
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"""
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',
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."""
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',
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
,
1443 env2
= Environment(BUILDERS
={'B1': b1
,
1447 # With no name, get_name will return the class. Allow
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:
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"""
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"""
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"""
1520 builder
= self
.builder
1524 builder(env
, target
='test3', source
=['test2.bar', 'test1.foo'])[0]
1525 except SCons
.Errors
.UserError
as 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"""
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]
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
)
1557 except SCons
.Errors
.UserError
as e
:
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
)
1565 builder(env
, target
='test5', source
=['test5.BAR2'])[0]
1566 except SCons
.Errors
.UserError
:
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"""
1574 foo_bld
= SCons
.Builder
.Builder(action
= 'a-foo',
1575 src_suffix
= '.ina',
1577 assert isinstance(foo_bld
, SCons
.Builder
.BuilderBase
)
1578 builder
= SCons
.Builder
.Builder(action
= { '.foo' : 'foo',
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',
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
)
1609 builder(env
, target
='t5', source
=['test5a.foo', 'test5b.inb'])[0]
1610 except SCons
.Errors
.UserError
as 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
1619 builder(env
, target
='t6', source
=['test6a.bar', 'test6b.ina'])[0]
1620 except SCons
.Errors
.UserError
as 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
1629 builder(env
, target
='t4', source
=['test4a.ina', 'test4b.inb'])[0]
1630 except SCons
.Errors
.UserError
as 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
1639 builder(env
, target
='t7', source
=[env
.fs
.File('test7')])[0]
1640 except SCons
.Errors
.UserError
as 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
1649 builder(env
, target
='t8', source
=['test8.unknown'])[0]
1650 except SCons
.Errors
.UserError
as 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__":
1662 # indent-tabs-mode:nil
1664 # vim: set expandtab tabstop=4 shiftwidth=4: