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.
27 Builder object subsystem.
29 A Builder object is a callable that encapsulates information about how
30 to execute actions to create a target Node (file) from source Nodes
31 (files), and how to create those dependencies for tracking.
33 The main entry point here is the Builder() factory method. This provides
34 a procedural interface that creates the right underlying Builder object
35 based on the keyword arguments supplied and the types of the arguments.
37 The goal is for this external interface to be simple enough that the
38 vast majority of users can create new Builders as necessary to support
39 building new types of files in their configurations, without having to
40 dive any deeper into this subsystem.
42 The base class here is BuilderBase. This is a concrete base class which
43 does, in fact, represent the Builder objects that we (or users) create.
45 There is also a proxy that looks like a Builder:
49 This proxies for a Builder with an action that is actually a
50 dictionary that knows how to map file suffixes to a specific
51 action. This is so that we can invoke different actions
52 (compilers, compile options) for different flavors of source
55 Builders and their proxies have the following public interface methods
56 used by other modules:
59 THE public interface. Calling a Builder object (with the
60 use of internal helper methods) sets up the target and source
61 dependencies, appropriate mapping to a specific action, and the
62 environment manipulation necessary for overridden construction
63 variable. This also takes care of warning about possible mistakes
67 Adds an emitter for a specific file suffix, used by some Tool
68 modules to specify that (for example) a yacc invocation on a .y
69 can create a .h *and* a .c file.
72 Adds an action for a specific file suffix, heavily used by
73 Tool modules to add their specific action(s) for turning
74 a source file into an object file to the global static
75 and shared object file Builders.
77 There are the following methods for internal use within this module:
80 The internal method that handles the heavily lifting when a
81 Builder is called. This is used so that the __call__() methods
82 can set up warning about possible mistakes in keyword-argument
83 overrides, and *then* execute all of the steps necessary so that
84 the warnings only occur once.
87 Returns the Builder's name within a specific Environment,
88 primarily used to try to return helpful information in error
96 Miscellaneous stuff for handling the prefix and suffix
97 manipulation we use in turning source file names into target
102 from collections
import UserDict
, UserList
106 import SCons
.Executor
109 import SCons
.Warnings
110 from SCons
.Debug
import logInstanceCreation
111 from SCons
.Errors
import InternalError
, UserError
118 def match_splitext(path
, suffixes
= []):
120 matchsuf
= [S
for S
in suffixes
if path
[-len(S
):] == S
]
122 suf
= max([(len(_f
),_f
) for _f
in matchsuf
])[1]
123 return [path
[:-len(suf
)], path
[-len(suf
):]]
124 return SCons
.Util
.splitext(path
)
126 class DictCmdGenerator(SCons
.Util
.Selector
):
127 """This is a callable class that can be used as a
128 command generator function. It holds on to a dictionary
129 mapping file suffixes to Actions. It uses that dictionary
130 to return the proper action based on the file suffix of
133 def __init__(self
, mapping
=None, source_ext_match
: bool=True) -> None:
134 super().__init
__(mapping
)
135 self
.source_ext_match
= source_ext_match
137 def src_suffixes(self
):
138 return list(self
.keys())
140 def add_action(self
, suffix
, action
) -> None:
141 """Add a suffix-action pair to the mapping.
143 self
[suffix
] = action
145 def __call__(self
, target
, source
, env
, for_signature
):
149 if self
.source_ext_match
:
150 suffixes
= self
.src_suffixes()
152 for src
in map(str, source
):
153 my_ext
= match_splitext(src
, suffixes
)[1]
154 if ext
and my_ext
!= ext
:
155 raise UserError("While building `%s' from `%s': Cannot build multiple sources with different extensions: %s, %s"
156 % (repr(list(map(str, target
))), src
, ext
, my_ext
))
159 ext
= match_splitext(str(source
[0]), self
.src_suffixes())[1]
163 raise UserError("While building `%s': "
164 "Cannot deduce file extension from source files: %s"
165 % (repr(list(map(str, target
))), repr(list(map(str, source
)))))
168 ret
= SCons
.Util
.Selector
.__call
__(self
, env
, source
, ext
)
169 except KeyError as e
:
170 raise UserError("Ambiguous suffixes after environment substitution: %s == %s == %s" % (e
.args
[0], e
.args
[1], e
.args
[2]))
172 raise UserError("While building `%s' from `%s': Don't know how to build from a source file with suffix `%s'. Expected a suffix in this list: %s." % \
173 (repr(list(map(str, target
))), repr(list(map(str, source
))), ext
, repr(list(self
.keys()))))
176 class CallableSelector(SCons
.Util
.Selector
):
177 """A callable dictionary that will, in turn, call the value it
179 def __call__(self
, env
, source
):
180 value
= SCons
.Util
.Selector
.__call
__(self
, env
, source
)
182 value
= value(env
, source
)
185 class DictEmitter(SCons
.Util
.Selector
):
186 """A callable dictionary that maps file suffixes to emitters.
187 When called, it finds the right emitter in its dictionary for the
188 suffix of the first source file, and calls that emitter to get the
189 right lists of targets and sources to return. If there's no emitter
190 for the suffix in its dictionary, the original target and source are
193 def __call__(self
, target
, source
, env
):
194 emitter
= SCons
.Util
.Selector
.__call
__(self
, env
, source
)
196 target
, source
= emitter(target
, source
, env
)
197 return (target
, source
)
199 class ListEmitter(UserList
):
200 """A callable list of emitters that calls each in sequence,
201 returning the result.
203 def __call__(self
, target
, source
, env
):
205 target
, source
= e(target
, source
, env
)
206 return (target
, source
)
208 # These are a common errors when calling a Builder;
209 # they are similar to the 'target' and 'source' keyword args to builders,
210 # so we issue warnings when we see them. The warnings can, of course,
212 misleading_keywords
= {
213 'targets' : 'target',
214 'sources' : 'source',
217 class OverrideWarner(UserDict
):
218 """A class for warning about keyword arguments that we use as
219 overrides in a Builder call.
221 This class exists to handle the fact that a single Builder call
222 can actually invoke multiple builders. This class only emits the
223 warnings once, no matter how many Builders are invoked.
225 def __init__(self
, mapping
) -> None:
226 super().__init
__(mapping
)
227 if SCons
.Debug
.track_instances
: logInstanceCreation(self
, 'Builder.OverrideWarner')
228 self
.already_warned
= None
230 def warn(self
) -> None:
231 if self
.already_warned
:
233 for k
in self
.keys():
234 if k
in misleading_keywords
:
235 alt
= misleading_keywords
[k
]
236 msg
= "Did you mean to use `%s' instead of `%s'?" % (alt
, k
)
237 SCons
.Warnings
.warn(SCons
.Warnings
.MisleadingKeywordsWarning
, msg
)
238 self
.already_warned
= 1
241 """A factory for builder objects."""
243 if 'generator' in kw
:
245 raise UserError("You must not specify both an action and a generator.")
246 kw
['action'] = SCons
.Action
.CommandGeneratorAction(kw
['generator'], {})
249 source_ext_match
= kw
.get('source_ext_match', True)
250 if 'source_ext_match' in kw
:
251 del kw
['source_ext_match']
252 if SCons
.Util
.is_Dict(kw
['action']):
253 composite
= DictCmdGenerator(kw
['action'], source_ext_match
)
254 kw
['action'] = SCons
.Action
.CommandGeneratorAction(composite
, {})
255 kw
['src_suffix'] = composite
.src_suffixes()
257 kw
['action'] = SCons
.Action
.Action(kw
['action'])
260 emitter
= kw
['emitter']
261 if SCons
.Util
.is_String(emitter
):
262 # This allows users to pass in an Environment
263 # variable reference (like "$FOO") as an emitter.
264 # We will look in that Environment variable for
265 # a callable to use as the actual emitter.
266 var
= SCons
.Util
.get_environment_var(emitter
)
268 raise UserError("Supplied emitter '%s' does not appear to refer to an Environment variable" % emitter
)
269 kw
['emitter'] = EmitterProxy(var
)
270 elif SCons
.Util
.is_Dict(emitter
):
271 kw
['emitter'] = DictEmitter(emitter
)
272 elif SCons
.Util
.is_List(emitter
):
273 kw
['emitter'] = ListEmitter(emitter
)
275 result
= BuilderBase(**kw
)
277 if composite
is not None:
278 result
= CompositeBuilder(result
, composite
)
282 def _node_errors(builder
, env
, tlist
, slist
):
283 """Validate that the lists of target and source nodes are
284 legal for this builder and environment. Raise errors or
285 issue warnings as appropriate.
288 # First, figure out if there are any errors in the way the targets
292 raise UserError("Multiple ways to build the same target were specified for: %s" % t
)
293 if t
.has_explicit_builder():
294 # Check for errors when the environments are different
295 # No error if environments are the same Environment instance
296 if (t
.env
is not None and t
.env
is not env
and
297 # Check OverrideEnvironment case - no error if wrapped Environments
298 # are the same instance, and overrides lists match
299 not (getattr(t
.env
, '__subject', 0) is getattr(env
, '__subject', 1) and
300 getattr(t
.env
, 'overrides', 0) == getattr(env
, 'overrides', 1) and
302 action
= t
.builder
.action
303 t_contents
= t
.builder
.action
.get_contents(tlist
, slist
, t
.env
)
304 contents
= builder
.action
.get_contents(tlist
, slist
, env
)
306 if t_contents
== contents
:
307 msg
= "Two different environments were specified for target %s,\n\tbut they appear to have the same action: %s" % (t
, action
.genstring(tlist
, slist
, t
.env
))
308 SCons
.Warnings
.warn(SCons
.Warnings
.DuplicateEnvironmentWarning
, msg
)
311 msg
= "Two environments with different actions were specified for the same target: %s\n(action 1: %s)\n(action 2: %s)" % (t
,t_contents
.decode('utf-8'),contents
.decode('utf-8'))
312 except UnicodeDecodeError:
313 msg
= "Two environments with different actions were specified for the same target: %s"%t
316 if t
.builder
!= builder
:
317 msg
= "Two different builders (%s and %s) were specified for the same target: %s" % (t
.builder
.get_name(env
), builder
.get_name(env
), t
)
319 # TODO(batch): list constructed each time!
320 if t
.get_executor().get_all_targets() != tlist
:
321 msg
= "Two different target lists have a target in common: %s (from %s and from %s)" % (t
, list(map(str, t
.get_executor().get_all_targets())), list(map(str, tlist
)))
323 elif t
.sources
!= slist
:
324 msg
= "Multiple ways to build the same target were specified for: %s (from %s and from %s)" % (t
, list(map(str, t
.sources
)), list(map(str, slist
)))
327 if builder
.single_source
:
329 raise UserError("More than one source given for single-source builder: targets=%s sources=%s" % (list(map(str,tlist
)), list(map(str,slist
))))
332 """This is a callable class that can act as a
333 Builder emitter. It holds on to a string that
334 is a key into an Environment dictionary, and will
335 look there at actual build time to see if it holds
336 a callable. If so, we will call that as the actual
338 def __init__(self
, var
) -> None:
339 self
.var
= SCons
.Util
.to_String(var
)
341 def __call__(self
, target
, source
, env
):
344 # Recursively substitute the variable.
345 # We can't use env.subst() because it deals only
346 # in strings. Maybe we should change that?
347 while SCons
.Util
.is_String(emitter
) and emitter
in env
:
348 emitter
= env
[emitter
]
349 if callable(emitter
):
350 target
, source
= emitter(target
, source
, env
)
351 elif SCons
.Util
.is_List(emitter
):
353 target
, source
= e(target
, source
, env
)
355 return (target
, source
)
357 def __eq__(self
, other
):
358 return self
.var
== other
.var
360 def __lt__(self
, other
):
361 return self
.var
< other
.var
363 def __le__(self
, other
):
364 return self
.var
<= other
.var
366 def __gt__(self
, other
):
367 return self
.var
> other
.var
369 def __ge__(self
, other
):
370 return self
.var
>= other
.var
373 """Base class for Builders, objects that create output
374 nodes (files) from input nodes (files).
377 def __init__(self
, action
= None,
380 src_suffix
: str = '',
381 target_factory
= None,
382 source_factory
= None,
383 target_scanner
= None,
384 source_scanner
= None,
388 single_source
: bool = False,
391 is_explicit
: bool = True,
393 ensure_suffix
: bool = False,
394 **overrides
) -> None:
395 if SCons
.Debug
.track_instances
: logInstanceCreation(self
, 'Builder.BuilderBase')
399 if SCons
.Util
.is_Dict(prefix
):
400 prefix
= CallableSelector(prefix
)
402 if SCons
.Util
.is_Dict(suffix
):
403 suffix
= CallableSelector(suffix
)
405 self
.single_source
= single_source
406 if 'overrides' in overrides
:
407 msg
= "The \"overrides\" keyword to Builder() creation has been removed;\n" +\
408 "\tspecify the items as keyword arguments to the Builder() call instead."
410 if 'scanner' in overrides
:
411 msg
= "The \"scanner\" keyword to Builder() creation has been removed;\n" +\
412 "\tuse: source_scanner or target_scanner as appropriate."
414 self
.overrides
= overrides
416 self
.set_suffix(suffix
)
417 self
.set_src_suffix(src_suffix
)
418 self
.ensure_suffix
= ensure_suffix
420 self
.target_factory
= target_factory
421 self
.source_factory
= source_factory
422 self
.target_scanner
= target_scanner
423 self
.source_scanner
= source_scanner
425 self
.emitter
= emitter
427 # Optional Builder name should only be used for Builders
428 # that don't get attached to construction environments.
431 self
.executor_kw
= {}
432 if chdir
is not _null
:
433 self
.executor_kw
['chdir'] = chdir
434 self
.is_explicit
= is_explicit
436 if src_builder
is None:
438 elif not SCons
.Util
.is_List(src_builder
):
439 src_builder
= [ src_builder
]
440 self
.src_builder
= src_builder
442 def __bool__(self
) -> bool:
443 raise InternalError("Do not test for the Node.builder attribute directly; use Node.has_builder() instead")
445 def get_name(self
, env
):
446 """Attempts to get the name of the Builder.
448 Look at the BUILDERS variable of env, expecting it to be a
449 dictionary containing this Builder, and return the key of the
450 dictionary. If there's no key, then return a directly-configured
451 name (if there is one) or the name of the class (by default)."""
454 index
= list(env
['BUILDERS'].values()).index(self
)
455 return list(env
['BUILDERS'].keys())[index
]
456 except (AttributeError, KeyError, TypeError, ValueError):
459 except AttributeError:
460 return str(self
.__class
__)
462 def __eq__(self
, other
):
463 return self
.__dict
__ == other
.__dict
__
465 def splitext(self
, path
, env
=None):
469 suffixes
= self
.src_suffixes(env
)
472 return match_splitext(path
, suffixes
)
474 def _adjustixes(self
, files
, pre
, suf
, ensure_suffix
: bool=False):
478 if not SCons
.Util
.is_List(files
):
482 if SCons
.Util
.is_String(f
):
483 f
= SCons
.Util
.adjustixes(f
, pre
, suf
, ensure_suffix
)
487 def _create_nodes(self
, env
, target
= None, source
= None):
488 """Create and return lists of target and source nodes.
490 src_suf
= self
.get_src_suffix(env
)
492 target_factory
= env
.get_factory(self
.target_factory
)
493 source_factory
= env
.get_factory(self
.source_factory
)
495 source
= self
._adjustixes
(source
, None, src_suf
)
496 slist
= env
.arg2nodes(source
, source_factory
)
498 pre
= self
.get_prefix(env
, slist
)
499 suf
= self
.get_suffix(env
, slist
)
503 t_from_s
= slist
[0].target_from_source
504 except AttributeError:
505 raise UserError("Do not know how to create a target from source `%s'" % slist
[0])
509 splitext
= lambda S
: self
.splitext(S
,env
)
510 tlist
= [ t_from_s(pre
, suf
, splitext
) ]
512 # orig_target = target
513 target
= self
._adjustixes
(target
, pre
, suf
, self
.ensure_suffix
)
514 tlist
= env
.arg2nodes(target
, target_factory
, target
=target
, source
=source
)
517 # The emitter is going to do str(node), but because we're
518 # being called *from* a builder invocation, the new targets
519 # don't yet have a builder set on them and will look like
520 # source files. Fool the emitter's str() calls by setting
521 # up a temporary builder on the new targets.
524 if not t
.is_derived():
526 new_targets
.append(t
)
528 orig_tlist
= tlist
[:]
529 orig_slist
= slist
[:]
531 target
, source
= self
.emitter(target
=tlist
, source
=slist
, env
=env
)
533 # Now delete the temporary builders that we attached to any
534 # new targets, so that _node_errors() doesn't do weird stuff
535 # to them because it thinks they already have builders.
536 for t
in new_targets
:
537 if t
.builder
is self
:
538 # Only delete the temporary builder if the emitter
539 # didn't change it on us.
542 # Have to call arg2nodes yet again, since it is legal for
543 # emitters to spit out strings as well as Node instances.
544 tlist
= env
.arg2nodes(target
, target_factory
,
545 target
=orig_tlist
, source
=orig_slist
)
546 slist
= env
.arg2nodes(source
, source_factory
,
547 target
=orig_tlist
, source
=orig_slist
)
551 def _execute(self
, env
, target
, source
, overwarn
={}, executor_kw
={}):
552 # We now assume that target and source are lists or None.
554 source
= self
.src_builder_sources(env
, source
, overwarn
)
556 if self
.single_source
and len(source
) > 1 and target
is None:
558 if target
is None: target
= [None]*len(source
)
559 for tgt
, src
in zip(target
, source
):
564 result
.extend(self
._execute
(env
, tgt
, src
, overwarn
))
565 return SCons
.Node
.NodeList(result
)
569 tlist
, slist
= self
._create
_nodes
(env
, target
, source
)
571 # If there is more than one target ensure that if we need to reset
572 # the implicit list to new scan of dependency all targets implicit lists
573 # are cleared. (SCons GH Issue #2811 and MongoDB SERVER-33111)
576 t
.target_peers
= tlist
578 # Check for errors with the specified target/source lists.
579 _node_errors(self
, env
, tlist
, slist
)
581 # The targets are fine, so find or make the appropriate Executor to
582 # build this particular list of targets from this particular list of
590 executor
= tlist
[0].get_executor(create
= 0)
591 except (AttributeError, IndexError):
594 executor
.add_sources(slist
)
598 fmt
= "Builder %s must have an action to build %s."
599 raise UserError(fmt
% (self
.get_name(env
or self
.env
),
600 list(map(str,tlist
))))
601 key
= self
.action
.batch_key(env
or self
.env
, tlist
, slist
)
604 executor
= SCons
.Executor
.GetBatchExecutor(key
)
608 executor
.add_batch(tlist
, slist
)
611 executor
= SCons
.Executor
.Executor(self
.action
, env
, [],
612 tlist
, slist
, executor_kw
)
614 SCons
.Executor
.AddBatchExecutor(key
, executor
)
616 # Now set up the relevant information in the target Nodes themselves.
618 t
.cwd
= env
.fs
.getcwd()
622 t
.set_executor(executor
)
623 t
.set_explicit(self
.is_explicit
)
625 if env
.get("SCONF_NODE"):
627 node
.attributes
.conftest_node
= 1
629 return SCons
.Node
.NodeList(tlist
)
631 def __call__(self
, env
, target
=None, source
=None, chdir
=_null
, **kw
):
632 # We now assume that target and source are lists or None.
633 # The caller (typically Environment.BuilderWrapper) is
634 # responsible for converting any scalar values to lists.
636 ekw
= self
.executor_kw
638 ekw
= self
.executor_kw
.copy()
640 if 'chdir' in ekw
and SCons
.Util
.is_String(ekw
['chdir']):
641 ekw
['chdir'] = env
.subst(ekw
['chdir'])
644 def prependDirIfRelative(f
, srcdir
=kw
['srcdir']):
646 if SCons
.Util
.is_String(f
) and not os
.path
.isabs(f
):
647 f
= os
.path
.join(srcdir
, f
)
649 if not SCons
.Util
.is_List(source
):
651 source
= list(map(prependDirIfRelative
, source
))
654 env_kw
= self
.overrides
.copy()
659 env_kw
= self
.overrides
661 # TODO if env_kw: then the following line. there's no purpose in calling if no overrides.
662 env
= env
.Override(env_kw
)
663 return self
._execute
(env
, target
, source
, OverrideWarner(kw
), ekw
)
665 def adjust_suffix(self
, suff
):
666 if suff
and not suff
[0] in [ '.', '_', '$' ]:
670 def get_prefix(self
, env
, sources
=[]):
673 prefix
= prefix(env
, sources
)
674 return env
.subst(prefix
)
676 def set_suffix(self
, suffix
) -> None:
677 if not callable(suffix
):
678 suffix
= self
.adjust_suffix(suffix
)
681 def get_suffix(self
, env
, sources
=[]):
684 suffix
= suffix(env
, sources
)
685 return env
.subst(suffix
)
687 def set_src_suffix(self
, src_suffix
) -> None:
690 elif not SCons
.Util
.is_List(src_suffix
):
691 src_suffix
= [ src_suffix
]
692 self
.src_suffix
= [callable(suf
) and suf
or self
.adjust_suffix(suf
) for suf
in src_suffix
]
694 def get_src_suffix(self
, env
):
695 """Get the first src_suffix in the list of src_suffixes."""
696 ret
= self
.src_suffixes(env
)
701 def add_emitter(self
, suffix
, emitter
) -> None:
702 """Add a suffix-emitter mapping to this Builder.
704 This assumes that emitter has been initialized with an
705 appropriate dictionary type, and will throw a TypeError if
706 not, so the caller is responsible for knowing that this is an
707 appropriate method to call for the Builder in question.
709 self
.emitter
[suffix
] = emitter
711 def add_src_builder(self
, builder
) -> None:
713 Add a new Builder to the list of src_builders.
715 This requires wiping out cached values so that the computed
716 lists of source suffixes get re-calculated.
719 self
.src_builder
.append(builder
)
721 def _get_sdict(self
, env
):
723 Returns a dictionary mapping all of the source suffixes of all
724 src_builders of this Builder to the underlying Builder that
725 should be called first.
727 This dictionary is used for each target specified, so we save a
728 lot of extra computation by memoizing it for each construction
731 Note that this is re-computed each time, not cached, because there
732 might be changes to one of our source Builders (or one of their
733 source Builders, and so on, and so on...) that we can't "see."
735 The underlying methods we call cache their computed values,
736 though, so we hope repeatedly aggregating them into a dictionary
737 like this won't be too big a hit. We may need to look for a
738 better way to do this if performance data show this has turned
739 into a significant bottleneck.
742 for bld
in self
.get_src_builders(env
):
743 for suf
in bld
.src_suffixes(env
):
747 def src_builder_sources(self
, env
, source
, overwarn
={}):
748 sdict
= self
._get
_sdict
(env
)
750 src_suffixes
= self
.src_suffixes(env
)
752 lengths
= list(set(map(len, src_suffixes
)))
754 def match_src_suffix(name
, src_suffixes
=src_suffixes
, lengths
=lengths
):
755 node_suffixes
= [name
[-l
:] for l
in lengths
]
756 for suf
in src_suffixes
:
757 if suf
in node_suffixes
:
762 for s
in SCons
.Util
.flatten(source
):
763 if SCons
.Util
.is_String(s
):
764 match_suffix
= match_src_suffix(env
.subst(s
))
765 if not match_suffix
and '.' not in s
:
766 src_suf
= self
.get_src_suffix(env
)
767 s
= self
._adjustixes
(s
, None, src_suf
)[0]
769 match_suffix
= match_src_suffix(s
.name
)
772 bld
= sdict
[match_suffix
]
776 tlist
= bld
._execute
(env
, None, [s
], overwarn
)
777 # If the subsidiary Builder returned more than one
778 # target, then filter out any sources that this
779 # Builder isn't capable of building.
781 tlist
= [t
for t
in tlist
if match_src_suffix(t
.name
)]
786 source_factory
= env
.get_factory(self
.source_factory
)
788 return env
.arg2nodes(result
, source_factory
)
790 def _get_src_builders_key(self
, env
):
793 @SCons.Memoize
.CountDictCall(_get_src_builders_key
)
794 def get_src_builders(self
, env
):
796 Returns the list of source Builders for this Builder.
798 This exists mainly to look up Builders referenced as
799 strings in the 'BUILDER' variable of the construction
800 environment and cache the result.
804 memo_dict
= self
._memo
['get_src_builders']
807 self
._memo
['get_src_builders'] = memo_dict
810 return memo_dict
[memo_key
]
815 for bld
in self
.src_builder
:
816 if SCons
.Util
.is_String(bld
):
818 bld
= env
['BUILDERS'][bld
]
823 memo_dict
[memo_key
] = builders
826 def _subst_src_suffixes_key(self
, env
):
829 @SCons.Memoize
.CountDictCall(_subst_src_suffixes_key
)
830 def subst_src_suffixes(self
, env
):
832 The suffix list may contain construction variable expansions,
833 so we have to evaluate the individual strings. To avoid doing
834 this over and over, we memoize the results for each construction
839 memo_dict
= self
._memo
['subst_src_suffixes']
842 self
._memo
['subst_src_suffixes'] = memo_dict
845 return memo_dict
[memo_key
]
848 suffixes
= [env
.subst(x
) for x
in self
.src_suffix
]
849 memo_dict
[memo_key
] = suffixes
852 def src_suffixes(self
, env
):
854 Returns the list of source suffixes for all src_builders of this
857 This is essentially a recursive descent of the src_builder "tree."
858 (This value isn't cached because there may be changes in a
859 src_builder many levels deep that we can't see.)
862 suffixes
= self
.subst_src_suffixes(env
)
865 for builder
in self
.get_src_builders(env
):
866 for s
in builder
.src_suffixes(env
):
872 class CompositeBuilder(SCons
.Util
.Proxy
):
873 """A Builder Proxy whose main purpose is to always have
874 a DictCmdGenerator as its action, and to provide access
875 to the DictCmdGenerator's add_action() method.
878 def __init__(self
, builder
, cmdgen
) -> None:
879 if SCons
.Debug
.track_instances
: logInstanceCreation(self
, 'Builder.CompositeBuilder')
880 super().__init
__(builder
)
882 # cmdgen should always be an instance of DictCmdGenerator.
884 self
.builder
= builder
886 __call__
= SCons
.Util
.Delegate('__call__')
888 def add_action(self
, suffix
, action
) -> None:
889 self
.cmdgen
.add_action(suffix
, action
)
890 self
.set_src_suffix(self
.cmdgen
.src_suffixes())
892 def is_a_Builder(obj
) -> bool:
893 """"Returns True if the specified obj is one of our Builder classes.
895 The test is complicated a bit by the fact that CompositeBuilder
896 is a proxy, not a subclass of BuilderBase.
898 return (isinstance(obj
, BuilderBase
)
899 or isinstance(obj
, CompositeBuilder
)
904 # indent-tabs-mode:nil
906 # vim: set expandtab tabstop=4 shiftwidth=4: