updated copyright on README
[scons.git] / SCons / Builder.py
blob3efcc8271df4b9c422084d26d99e3c725834f352
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 """
25 SCons.Builder
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:
47 CompositeBuilder
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
53 files.
55 Builders and their proxies have the following public interface methods
56 used by other modules:
58 - __call__()
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
64 in keyword arguments.
66 - add_emitter()
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.
71 - add_action()
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:
79 - _execute()
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.
86 - get_name()
87 Returns the Builder's name within a specific Environment,
88 primarily used to try to return helpful information in error
89 messages.
91 - adjust_suffix()
92 - get_prefix()
93 - get_suffix()
94 - get_src_suffix()
95 - set_src_suffix()
96 Miscellaneous stuff for handling the prefix and suffix
97 manipulation we use in turning source file names into target
98 file names.
102 import os
103 from collections import UserDict, UserList
104 from contextlib import suppress
105 from typing import Optional
107 import SCons.Action
108 import SCons.Debug
109 import SCons.Executor
110 import SCons.Memoize
111 import SCons.Util
112 import SCons.Warnings
113 from SCons.Debug import logInstanceCreation
114 from SCons.Errors import InternalError, UserError
115 from SCons.Util.sctyping import ExecutorType
117 class _Null:
118 pass
120 _null = _Null
122 def match_splitext(path, suffixes = []):
123 if suffixes:
124 matchsuf = [S for S in suffixes if path[-len(S):] == S]
125 if matchsuf:
126 suf = max([(len(_f),_f) for _f in matchsuf])[1]
127 return [path[:-len(suf)], path[-len(suf):]]
128 return SCons.Util.splitext(path)
130 class DictCmdGenerator(SCons.Util.Selector):
131 """This is a callable class that can be used as a
132 command generator function. It holds on to a dictionary
133 mapping file suffixes to Actions. It uses that dictionary
134 to return the proper action based on the file suffix of
135 the source file."""
137 def __init__(self, mapping=None, source_ext_match: bool=True) -> None:
138 super().__init__(mapping)
139 self.source_ext_match = source_ext_match
141 def src_suffixes(self):
142 return list(self.keys())
144 def add_action(self, suffix, action) -> None:
145 """Add a suffix-action pair to the mapping.
147 self[suffix] = action
149 def __call__(self, target, source, env, for_signature):
150 if not source:
151 return []
153 if self.source_ext_match:
154 suffixes = self.src_suffixes()
155 ext = None
156 for src in map(str, source):
157 my_ext = match_splitext(src, suffixes)[1]
158 if ext and my_ext != ext:
159 raise UserError("While building `%s' from `%s': Cannot build multiple sources with different extensions: %s, %s"
160 % (repr(list(map(str, target))), src, ext, my_ext))
161 ext = my_ext
162 else:
163 ext = match_splitext(str(source[0]), self.src_suffixes())[1]
165 if not ext:
166 #return ext
167 raise UserError("While building `%s': "
168 "Cannot deduce file extension from source files: %s"
169 % (repr(list(map(str, target))), repr(list(map(str, source)))))
171 try:
172 ret = SCons.Util.Selector.__call__(self, env, source, ext)
173 except KeyError as e:
174 raise UserError("Ambiguous suffixes after environment substitution: %s == %s == %s" % (e.args[0], e.args[1], e.args[2]))
175 if ret is None:
176 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." % \
177 (repr(list(map(str, target))), repr(list(map(str, source))), ext, repr(list(self.keys()))))
178 return ret
180 class CallableSelector(SCons.Util.Selector):
181 """A callable dictionary that will, in turn, call the value it
182 finds if it can."""
183 def __call__(self, env, source):
184 value = SCons.Util.Selector.__call__(self, env, source)
185 if callable(value):
186 value = value(env, source)
187 return value
189 class DictEmitter(SCons.Util.Selector):
190 """A callable dictionary that maps file suffixes to emitters.
191 When called, it finds the right emitter in its dictionary for the
192 suffix of the first source file, and calls that emitter to get the
193 right lists of targets and sources to return. If there's no emitter
194 for the suffix in its dictionary, the original target and source are
195 returned.
197 def __call__(self, target, source, env):
198 emitter = SCons.Util.Selector.__call__(self, env, source)
199 if emitter:
200 target, source = emitter(target, source, env)
201 return (target, source)
203 class ListEmitter(UserList):
204 """A callable list of emitters that calls each in sequence,
205 returning the result.
207 def __call__(self, target, source, env):
208 for e in self.data:
209 target, source = e(target, source, env)
210 return (target, source)
212 # These are a common errors when calling a Builder;
213 # they are similar to the 'target' and 'source' keyword args to builders,
214 # so we issue warnings when we see them. The warnings can, of course,
215 # be disabled.
216 misleading_keywords = {
217 'targets' : 'target',
218 'sources' : 'source',
221 class OverrideWarner(UserDict):
222 """A class for warning about keyword arguments that we use as
223 overrides in a Builder call.
225 This class exists to handle the fact that a single Builder call
226 can actually invoke multiple builders. This class only emits the
227 warnings once, no matter how many Builders are invoked.
229 def __init__(self, mapping) -> None:
230 super().__init__(mapping)
231 if SCons.Debug.track_instances: logInstanceCreation(self, 'Builder.OverrideWarner')
232 self.already_warned = None
234 def warn(self) -> None:
235 if self.already_warned:
236 return
237 for k in self.keys():
238 if k in misleading_keywords:
239 alt = misleading_keywords[k]
240 msg = "Did you mean to use `%s' instead of `%s'?" % (alt, k)
241 SCons.Warnings.warn(SCons.Warnings.MisleadingKeywordsWarning, msg)
242 self.already_warned = 1
244 def Builder(**kw):
245 """A factory for builder objects."""
246 composite = None
247 if 'generator' in kw:
248 if 'action' in kw:
249 raise UserError("You must not specify both an action and a generator.")
250 kw['action'] = SCons.Action.CommandGeneratorAction(kw['generator'], {})
251 del kw['generator']
252 elif 'action' in kw:
253 source_ext_match = kw.get('source_ext_match', True)
254 if 'source_ext_match' in kw:
255 del kw['source_ext_match']
256 if SCons.Util.is_Dict(kw['action']):
257 composite = DictCmdGenerator(kw['action'], source_ext_match)
258 kw['action'] = SCons.Action.CommandGeneratorAction(composite, {})
259 kw['src_suffix'] = composite.src_suffixes()
260 else:
261 kw['action'] = SCons.Action.Action(kw['action'])
263 if 'emitter' in kw:
264 emitter = kw['emitter']
265 if SCons.Util.is_String(emitter):
266 # This allows users to pass in an Environment
267 # variable reference (like "$FOO") as an emitter.
268 # We will look in that Environment variable for
269 # a callable to use as the actual emitter.
270 var = SCons.Util.get_environment_var(emitter)
271 if not var:
272 raise UserError("Supplied emitter '%s' does not appear to refer to an Environment variable" % emitter)
273 kw['emitter'] = EmitterProxy(var)
274 elif SCons.Util.is_Dict(emitter):
275 kw['emitter'] = DictEmitter(emitter)
276 elif SCons.Util.is_List(emitter):
277 kw['emitter'] = ListEmitter(emitter)
279 result = BuilderBase(**kw)
281 if composite is not None:
282 result = CompositeBuilder(result, composite)
284 return result
286 def _node_errors(builder, env, tlist, slist):
287 """Validate that the lists of target and source nodes are
288 legal for this builder and environment. Raise errors or
289 issue warnings as appropriate.
292 # First, figure out if there are any errors in the way the targets
293 # were specified.
294 for t in tlist:
295 if t.side_effect:
296 raise UserError("Multiple ways to build the same target were specified for: %s" % t)
297 if t.has_explicit_builder():
298 # Check for errors when the environments are different
299 # No error if environments are the same Environment instance
300 if (t.env is not None and t.env is not env and
301 # Check OverrideEnvironment case - no error if wrapped Environments
302 # are the same instance, and overrides lists match
303 not (getattr(t.env, '__subject', 0) is getattr(env, '__subject', 1) and
304 getattr(t.env, 'overrides', 0) == getattr(env, 'overrides', 1) and
305 not builder.multi)):
306 action = t.builder.action
307 t_contents = t.builder.action.get_contents(tlist, slist, t.env)
308 contents = builder.action.get_contents(tlist, slist, env)
310 if t_contents == contents:
311 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))
312 SCons.Warnings.warn(SCons.Warnings.DuplicateEnvironmentWarning, msg)
313 else:
314 try:
315 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'))
316 except UnicodeDecodeError:
317 msg = "Two environments with different actions were specified for the same target: %s"%t
318 raise UserError(msg)
319 if builder.multi:
320 if t.builder != builder:
321 msg = "Two different builders (%s and %s) were specified for the same target: %s" % (t.builder.get_name(env), builder.get_name(env), t)
322 raise UserError(msg)
323 # TODO(batch): list constructed each time!
324 if t.get_executor().get_all_targets() != tlist:
325 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)))
326 raise UserError(msg)
327 elif t.sources != slist:
328 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)))
329 raise UserError(msg)
331 if builder.single_source:
332 if len(slist) > 1:
333 raise UserError("More than one source given for single-source builder: targets=%s sources=%s" % (list(map(str,tlist)), list(map(str,slist))))
335 class EmitterProxy:
336 """This is a callable class that can act as a
337 Builder emitter. It holds on to a string that
338 is a key into an Environment dictionary, and will
339 look there at actual build time to see if it holds
340 a callable. If so, we will call that as the actual
341 emitter."""
342 def __init__(self, var) -> None:
343 self.var = SCons.Util.to_String(var)
345 def __call__(self, target, source, env):
346 emitter = self.var
348 # Recursively substitute the variable.
349 # We can't use env.subst() because it deals only
350 # in strings. Maybe we should change that?
351 while SCons.Util.is_String(emitter) and emitter in env:
352 emitter = env[emitter]
353 if callable(emitter):
354 target, source = emitter(target, source, env)
355 elif SCons.Util.is_List(emitter):
356 for e in emitter:
357 target, source = e(target, source, env)
359 return (target, source)
361 def __eq__(self, other):
362 return self.var == other.var
364 def __lt__(self, other):
365 return self.var < other.var
367 def __le__(self, other):
368 return self.var <= other.var
370 def __gt__(self, other):
371 return self.var > other.var
373 def __ge__(self, other):
374 return self.var >= other.var
376 class BuilderBase:
377 """Base class for Builders, objects that create output
378 nodes (files) from input nodes (files).
381 def __init__(self, action = None,
382 prefix: str = '',
383 suffix: str = '',
384 src_suffix: str = '',
385 target_factory = None,
386 source_factory = None,
387 target_scanner = None,
388 source_scanner = None,
389 emitter = None,
390 multi: bool = False,
391 env = None,
392 single_source: bool = False,
393 name = None,
394 chdir = _null,
395 is_explicit: bool = True,
396 src_builder = None,
397 ensure_suffix: bool = False,
398 **overrides) -> None:
399 if SCons.Debug.track_instances: logInstanceCreation(self, 'Builder.BuilderBase')
400 self._memo = {}
401 self.action = action
402 self.multi = multi
403 if SCons.Util.is_Dict(prefix):
404 prefix = CallableSelector(prefix)
405 self.prefix = prefix
406 if SCons.Util.is_Dict(suffix):
407 suffix = CallableSelector(suffix)
408 self.env = env
409 self.single_source = single_source
410 if 'overrides' in overrides:
411 msg = "The \"overrides\" keyword to Builder() creation has been removed;\n" +\
412 "\tspecify the items as keyword arguments to the Builder() call instead."
413 raise TypeError(msg)
414 if 'scanner' in overrides:
415 msg = "The \"scanner\" keyword to Builder() creation has been removed;\n" +\
416 "\tuse: source_scanner or target_scanner as appropriate."
417 raise TypeError(msg)
418 self.overrides = overrides
420 self.set_suffix(suffix)
421 self.set_src_suffix(src_suffix)
422 self.ensure_suffix = ensure_suffix
424 self.target_factory = target_factory
425 self.source_factory = source_factory
426 self.target_scanner = target_scanner
427 self.source_scanner = source_scanner
429 self.emitter = emitter
431 # Optional Builder name should only be used for Builders
432 # that don't get attached to construction environments.
433 if name:
434 self.name = name
435 self.executor_kw = {}
436 if chdir is not _null:
437 self.executor_kw['chdir'] = chdir
438 self.is_explicit = is_explicit
440 if src_builder is None:
441 src_builder = []
442 elif not SCons.Util.is_List(src_builder):
443 src_builder = [ src_builder ]
444 self.src_builder = src_builder
446 def __bool__(self) -> bool:
447 raise InternalError("Do not test for the Node.builder attribute directly; use Node.has_builder() instead")
449 def get_name(self, env):
450 """Attempts to get the name of the Builder.
452 Look at the BUILDERS variable of env, expecting it to be a
453 dictionary containing this Builder, and return the key of the
454 dictionary. If there's no key, then return a directly-configured
455 name (if there is one) or the name of the class (by default)."""
457 try:
458 index = list(env['BUILDERS'].values()).index(self)
459 return list(env['BUILDERS'].keys())[index]
460 except (AttributeError, KeyError, TypeError, ValueError):
461 try:
462 return self.name
463 except AttributeError:
464 return str(self.__class__)
466 def __eq__(self, other):
467 return self.__dict__ == other.__dict__
469 def splitext(self, path, env=None):
470 if not env:
471 env = self.env
472 if env:
473 suffixes = self.src_suffixes(env)
474 else:
475 suffixes = []
476 return match_splitext(path, suffixes)
478 def _adjustixes(self, files, pre, suf, ensure_suffix: bool=False):
479 if not files:
480 return []
481 result = []
482 if not SCons.Util.is_List(files):
483 files = [files]
485 for f in files:
486 # fspath() is to catch PathLike paths. We avoid the simpler
487 # str(f) so as not to "lose" files that are already Nodes:
488 # TypeError: expected str, bytes or os.PathLike object, not File
489 with suppress(TypeError):
490 f = os.fspath(f)
491 if SCons.Util.is_String(f):
492 f = SCons.Util.adjustixes(f, pre, suf, ensure_suffix)
493 result.append(f)
494 return result
496 def _create_nodes(self, env, target = None, source = None):
497 """Create and return lists of target and source nodes.
499 src_suf = self.get_src_suffix(env)
501 target_factory = env.get_factory(self.target_factory)
502 source_factory = env.get_factory(self.source_factory)
504 source = self._adjustixes(source, None, src_suf)
505 slist = env.arg2nodes(source, source_factory)
507 pre = self.get_prefix(env, slist)
508 suf = self.get_suffix(env, slist)
510 if target is None:
511 try:
512 t_from_s = slist[0].target_from_source
513 except AttributeError:
514 raise UserError("Do not know how to create a target from source `%s'" % slist[0])
515 except IndexError:
516 tlist = []
517 else:
518 splitext = lambda S: self.splitext(S,env)
519 tlist = [ t_from_s(pre, suf, splitext) ]
520 else:
521 # orig_target = target
522 target = self._adjustixes(target, pre, suf, self.ensure_suffix)
523 tlist = env.arg2nodes(target, target_factory, target=target, source=source)
525 if self.emitter:
526 # The emitter is going to do str(node), but because we're
527 # being called *from* a builder invocation, the new targets
528 # don't yet have a builder set on them and will look like
529 # source files. Fool the emitter's str() calls by setting
530 # up a temporary builder on the new targets.
531 new_targets = []
532 for t in tlist:
533 if not t.is_derived():
534 t.builder_set(self)
535 new_targets.append(t)
537 orig_tlist = tlist[:]
538 orig_slist = slist[:]
540 target, source = self.emitter(target=tlist, source=slist, env=env)
542 # Now delete the temporary builders that we attached to any
543 # new targets, so that _node_errors() doesn't do weird stuff
544 # to them because it thinks they already have builders.
545 for t in new_targets:
546 if t.builder is self:
547 # Only delete the temporary builder if the emitter
548 # didn't change it on us.
549 t.builder_set(None)
551 # Have to call arg2nodes yet again, since it is legal for
552 # emitters to spit out strings as well as Node instances.
553 tlist = env.arg2nodes(target, target_factory,
554 target=orig_tlist, source=orig_slist)
555 slist = env.arg2nodes(source, source_factory,
556 target=orig_tlist, source=orig_slist)
558 return tlist, slist
560 def _execute(self, env, target, source, overwarn={}, executor_kw={}):
561 # We now assume that target and source are lists or None.
562 if self.src_builder:
563 source = self.src_builder_sources(env, source, overwarn)
565 if self.single_source and len(source) > 1 and target is None:
566 result = []
567 if target is None: target = [None]*len(source)
568 for tgt, src in zip(target, source):
569 if tgt is not None:
570 tgt = [tgt]
571 if src is not None:
572 src = [src]
573 result.extend(self._execute(env, tgt, src, overwarn))
574 return SCons.Node.NodeList(result)
576 overwarn.warn()
578 tlist, slist = self._create_nodes(env, target, source)
580 # If there is more than one target ensure that if we need to reset
581 # the implicit list to new scan of dependency all targets implicit lists
582 # are cleared. (SCons GH Issue #2811 and MongoDB SERVER-33111)
583 if len(tlist) > 1:
584 for t in tlist:
585 t.target_peers = tlist
587 # Check for errors with the specified target/source lists.
588 _node_errors(self, env, tlist, slist)
590 # The targets are fine, so find or make the appropriate Executor to
591 # build this particular list of targets from this particular list of
592 # sources.
594 executor: Optional[ExecutorType] = None
595 key = None
597 if self.multi:
598 try:
599 executor = tlist[0].get_executor(create = 0)
600 except (AttributeError, IndexError):
601 pass
602 else:
603 executor.add_sources(slist)
605 if executor is None:
606 if not self.action:
607 fmt = "Builder %s must have an action to build %s."
608 raise UserError(fmt % (self.get_name(env or self.env),
609 list(map(str,tlist))))
610 key = self.action.batch_key(env or self.env, tlist, slist)
611 if key:
612 try:
613 executor = SCons.Executor.GetBatchExecutor(key)
614 except KeyError:
615 pass
616 else:
617 executor.add_batch(tlist, slist)
619 if executor is None:
620 executor = SCons.Executor.Executor(self.action, env, [],
621 tlist, slist, executor_kw)
622 if key:
623 SCons.Executor.AddBatchExecutor(key, executor)
625 # Now set up the relevant information in the target Nodes themselves.
626 for t in tlist:
627 t.cwd = env.fs.getcwd()
628 t.builder_set(self)
629 t.env_set(env)
630 t.add_source(slist)
631 t.set_executor(executor)
632 t.set_explicit(self.is_explicit)
634 if env.get("SCONF_NODE"):
635 for node in tlist:
636 node.attributes.conftest_node = 1
638 return SCons.Node.NodeList(tlist)
640 def __call__(self, env, target=None, source=None, chdir=_null, **kw):
641 # We now assume that target and source are lists or None.
642 # The caller (typically Environment.BuilderWrapper) is
643 # responsible for converting any scalar values to lists.
644 if chdir is _null:
645 ekw = self.executor_kw
646 else:
647 ekw = self.executor_kw.copy()
648 ekw['chdir'] = chdir
649 if 'chdir' in ekw and SCons.Util.is_String(ekw['chdir']):
650 ekw['chdir'] = env.subst(ekw['chdir'])
651 if kw:
652 if 'srcdir' in kw:
653 def prependDirIfRelative(f, srcdir=kw['srcdir']):
654 import os.path
655 if SCons.Util.is_String(f) and not os.path.isabs(f):
656 f = os.path.join(srcdir, f)
657 return f
658 if not SCons.Util.is_List(source):
659 source = [source]
660 source = list(map(prependDirIfRelative, source))
661 del kw['srcdir']
662 if self.overrides:
663 env_kw = self.overrides.copy()
664 env_kw.update(kw)
665 else:
666 env_kw = kw
667 else:
668 env_kw = self.overrides
670 # TODO if env_kw: then the following line. there's no purpose in calling if no overrides.
671 env = env.Override(env_kw)
672 return self._execute(env, target, source, OverrideWarner(kw), ekw)
674 def adjust_suffix(self, suff):
675 if suff and not suff[0] in [ '.', '_', '$' ]:
676 return '.' + suff
677 return suff
679 def get_prefix(self, env, sources=[]):
680 prefix = self.prefix
681 if callable(prefix):
682 prefix = prefix(env, sources)
683 return env.subst(prefix)
685 def set_suffix(self, suffix) -> None:
686 if not callable(suffix):
687 suffix = self.adjust_suffix(suffix)
688 self.suffix = suffix
690 def get_suffix(self, env, sources=[]):
691 suffix = self.suffix
692 if callable(suffix):
693 suffix = suffix(env, sources)
694 return env.subst(suffix)
696 def set_src_suffix(self, src_suffix) -> None:
697 if not src_suffix:
698 src_suffix = []
699 elif not SCons.Util.is_List(src_suffix):
700 src_suffix = [ src_suffix ]
701 self.src_suffix = [callable(suf) and suf or self.adjust_suffix(suf) for suf in src_suffix]
703 def get_src_suffix(self, env):
704 """Get the first src_suffix in the list of src_suffixes."""
705 ret = self.src_suffixes(env)
706 if not ret:
707 return ''
708 return ret[0]
710 def add_emitter(self, suffix, emitter) -> None:
711 """Add a suffix-emitter mapping to this Builder.
713 This assumes that emitter has been initialized with an
714 appropriate dictionary type, and will throw a TypeError if
715 not, so the caller is responsible for knowing that this is an
716 appropriate method to call for the Builder in question.
718 self.emitter[suffix] = emitter
720 def add_src_builder(self, builder) -> None:
722 Add a new Builder to the list of src_builders.
724 This requires wiping out cached values so that the computed
725 lists of source suffixes get re-calculated.
727 self._memo = {}
728 self.src_builder.append(builder)
730 def _get_sdict(self, env):
732 Returns a dictionary mapping all of the source suffixes of all
733 src_builders of this Builder to the underlying Builder that
734 should be called first.
736 This dictionary is used for each target specified, so we save a
737 lot of extra computation by memoizing it for each construction
738 environment.
740 Note that this is re-computed each time, not cached, because there
741 might be changes to one of our source Builders (or one of their
742 source Builders, and so on, and so on...) that we can't "see."
744 The underlying methods we call cache their computed values,
745 though, so we hope repeatedly aggregating them into a dictionary
746 like this won't be too big a hit. We may need to look for a
747 better way to do this if performance data show this has turned
748 into a significant bottleneck.
750 sdict = {}
751 for bld in self.get_src_builders(env):
752 for suf in bld.src_suffixes(env):
753 sdict[suf] = bld
754 return sdict
756 def src_builder_sources(self, env, source, overwarn={}):
757 sdict = self._get_sdict(env)
759 src_suffixes = self.src_suffixes(env)
761 lengths = list(set(map(len, src_suffixes)))
763 def match_src_suffix(name, src_suffixes=src_suffixes, lengths=lengths):
764 node_suffixes = [name[-l:] for l in lengths]
765 for suf in src_suffixes:
766 if suf in node_suffixes:
767 return suf
768 return None
770 result = []
771 for s in SCons.Util.flatten(source):
772 if SCons.Util.is_String(s):
773 match_suffix = match_src_suffix(env.subst(s))
774 if not match_suffix and '.' not in s:
775 src_suf = self.get_src_suffix(env)
776 s = self._adjustixes(s, None, src_suf)[0]
777 else:
778 match_suffix = match_src_suffix(s.name)
779 if match_suffix:
780 try:
781 bld = sdict[match_suffix]
782 except KeyError:
783 result.append(s)
784 else:
785 tlist = bld._execute(env, None, [s], overwarn)
786 # If the subsidiary Builder returned more than one
787 # target, then filter out any sources that this
788 # Builder isn't capable of building.
789 if len(tlist) > 1:
790 tlist = [t for t in tlist if match_src_suffix(t.name)]
791 result.extend(tlist)
792 else:
793 result.append(s)
795 source_factory = env.get_factory(self.source_factory)
797 return env.arg2nodes(result, source_factory)
799 def _get_src_builders_key(self, env):
800 return id(env)
802 @SCons.Memoize.CountDictCall(_get_src_builders_key)
803 def get_src_builders(self, env):
805 Returns the list of source Builders for this Builder.
807 This exists mainly to look up Builders referenced as
808 strings in the 'BUILDER' variable of the construction
809 environment and cache the result.
811 memo_key = id(env)
812 try:
813 memo_dict = self._memo['get_src_builders']
814 except KeyError:
815 memo_dict = {}
816 self._memo['get_src_builders'] = memo_dict
817 else:
818 try:
819 return memo_dict[memo_key]
820 except KeyError:
821 pass
823 builders = []
824 for bld in self.src_builder:
825 if SCons.Util.is_String(bld):
826 try:
827 bld = env['BUILDERS'][bld]
828 except KeyError:
829 continue
830 builders.append(bld)
832 memo_dict[memo_key] = builders
833 return builders
835 def _subst_src_suffixes_key(self, env):
836 return id(env)
838 @SCons.Memoize.CountDictCall(_subst_src_suffixes_key)
839 def subst_src_suffixes(self, env):
841 The suffix list may contain construction variable expansions,
842 so we have to evaluate the individual strings. To avoid doing
843 this over and over, we memoize the results for each construction
844 environment.
846 memo_key = id(env)
847 try:
848 memo_dict = self._memo['subst_src_suffixes']
849 except KeyError:
850 memo_dict = {}
851 self._memo['subst_src_suffixes'] = memo_dict
852 else:
853 try:
854 return memo_dict[memo_key]
855 except KeyError:
856 pass
857 suffixes = [env.subst(x) for x in self.src_suffix]
858 memo_dict[memo_key] = suffixes
859 return suffixes
861 def src_suffixes(self, env):
863 Returns the list of source suffixes for all src_builders of this
864 Builder.
866 This is essentially a recursive descent of the src_builder "tree."
867 (This value isn't cached because there may be changes in a
868 src_builder many levels deep that we can't see.)
870 sdict = {}
871 suffixes = self.subst_src_suffixes(env)
872 for s in suffixes:
873 sdict[s] = 1
874 for builder in self.get_src_builders(env):
875 for s in builder.src_suffixes(env):
876 if s not in sdict:
877 sdict[s] = 1
878 suffixes.append(s)
879 return suffixes
881 class CompositeBuilder(SCons.Util.Proxy):
882 """A Builder Proxy whose main purpose is to always have
883 a DictCmdGenerator as its action, and to provide access
884 to the DictCmdGenerator's add_action() method.
887 def __init__(self, builder, cmdgen) -> None:
888 if SCons.Debug.track_instances: logInstanceCreation(self, 'Builder.CompositeBuilder')
889 super().__init__(builder)
891 # cmdgen should always be an instance of DictCmdGenerator.
892 self.cmdgen = cmdgen
893 self.builder = builder
895 __call__ = SCons.Util.Delegate('__call__')
897 def add_action(self, suffix, action) -> None:
898 self.cmdgen.add_action(suffix, action)
899 self.set_src_suffix(self.cmdgen.src_suffixes())
901 def is_a_Builder(obj) -> bool:
902 """"Returns True if the specified obj is one of our Builder classes.
904 The test is complicated a bit by the fact that CompositeBuilder
905 is a proxy, not a subclass of BuilderBase.
907 return (isinstance(obj, BuilderBase)
908 or isinstance(obj, CompositeBuilder)
909 or callable(obj))
911 # Local Variables:
912 # tab-width:4
913 # indent-tabs-mode:nil
914 # End:
915 # vim: set expandtab tabstop=4 shiftwidth=4: