Updates to PR 4374 from mwichmann to correct config file hash changes
[scons.git] / SCons / Builder.py
bloba11524ed69f369c8bf67c8a40789507c2acc0bd7
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 from collections import UserDict, UserList
104 import SCons.Action
105 import SCons.Debug
106 import SCons.Executor
107 import SCons.Memoize
108 import SCons.Util
109 import SCons.Warnings
110 from SCons.Debug import logInstanceCreation
111 from SCons.Errors import InternalError, UserError
113 class _Null:
114 pass
116 _null = _Null
118 def match_splitext(path, suffixes = []):
119 if suffixes:
120 matchsuf = [S for S in suffixes if path[-len(S):] == S]
121 if matchsuf:
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
131 the source file."""
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):
146 if not source:
147 return []
149 if self.source_ext_match:
150 suffixes = self.src_suffixes()
151 ext = None
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))
157 ext = my_ext
158 else:
159 ext = match_splitext(str(source[0]), self.src_suffixes())[1]
161 if not ext:
162 #return ext
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)))))
167 try:
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]))
171 if ret is None:
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()))))
174 return ret
176 class CallableSelector(SCons.Util.Selector):
177 """A callable dictionary that will, in turn, call the value it
178 finds if it can."""
179 def __call__(self, env, source):
180 value = SCons.Util.Selector.__call__(self, env, source)
181 if callable(value):
182 value = value(env, source)
183 return value
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
191 returned.
193 def __call__(self, target, source, env):
194 emitter = SCons.Util.Selector.__call__(self, env, source)
195 if emitter:
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):
204 for e in self.data:
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,
211 # be disabled.
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:
232 return
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
240 def Builder(**kw):
241 """A factory for builder objects."""
242 composite = None
243 if 'generator' in kw:
244 if 'action' in kw:
245 raise UserError("You must not specify both an action and a generator.")
246 kw['action'] = SCons.Action.CommandGeneratorAction(kw['generator'], {})
247 del kw['generator']
248 elif 'action' in kw:
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()
256 else:
257 kw['action'] = SCons.Action.Action(kw['action'])
259 if 'emitter' in kw:
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)
267 if not var:
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)
280 return result
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
289 # were specified.
290 for t in tlist:
291 if t.side_effect:
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
301 not builder.multi)):
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)
309 else:
310 try:
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
314 raise UserError(msg)
315 if builder.multi:
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)
318 raise UserError(msg)
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)))
322 raise UserError(msg)
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)))
325 raise UserError(msg)
327 if builder.single_source:
328 if len(slist) > 1:
329 raise UserError("More than one source given for single-source builder: targets=%s sources=%s" % (list(map(str,tlist)), list(map(str,slist))))
331 class EmitterProxy:
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
337 emitter."""
338 def __init__(self, var) -> None:
339 self.var = SCons.Util.to_String(var)
341 def __call__(self, target, source, env):
342 emitter = self.var
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):
352 for e in 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
372 class BuilderBase:
373 """Base class for Builders, objects that create output
374 nodes (files) from input nodes (files).
377 def __init__(self, action = None,
378 prefix: str = '',
379 suffix: str = '',
380 src_suffix: str = '',
381 target_factory = None,
382 source_factory = None,
383 target_scanner = None,
384 source_scanner = None,
385 emitter = None,
386 multi: int = 0,
387 env = None,
388 single_source: bool = False,
389 name = None,
390 chdir = _null,
391 is_explicit: bool = True,
392 src_builder = None,
393 ensure_suffix: bool = False,
394 **overrides) -> None:
395 if SCons.Debug.track_instances: logInstanceCreation(self, 'Builder.BuilderBase')
396 self._memo = {}
397 self.action = action
398 self.multi = multi
399 if SCons.Util.is_Dict(prefix):
400 prefix = CallableSelector(prefix)
401 self.prefix = prefix
402 if SCons.Util.is_Dict(suffix):
403 suffix = CallableSelector(suffix)
404 self.env = env
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."
409 raise TypeError(msg)
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."
413 raise TypeError(msg)
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.
429 if name:
430 self.name = name
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:
437 src_builder = []
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)."""
453 try:
454 index = list(env['BUILDERS'].values()).index(self)
455 return list(env['BUILDERS'].keys())[index]
456 except (AttributeError, KeyError, TypeError, ValueError):
457 try:
458 return self.name
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):
466 if not env:
467 env = self.env
468 if env:
469 suffixes = self.src_suffixes(env)
470 else:
471 suffixes = []
472 return match_splitext(path, suffixes)
474 def _adjustixes(self, files, pre, suf, ensure_suffix: bool=False):
475 if not files:
476 return []
477 result = []
478 if not SCons.Util.is_List(files):
479 files = [files]
481 for f in files:
482 if SCons.Util.is_String(f):
483 f = SCons.Util.adjustixes(f, pre, suf, ensure_suffix)
484 result.append(f)
485 return result
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)
501 if target is None:
502 try:
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])
506 except IndexError:
507 tlist = []
508 else:
509 splitext = lambda S: self.splitext(S,env)
510 tlist = [ t_from_s(pre, suf, splitext) ]
511 else:
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)
516 if self.emitter:
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.
522 new_targets = []
523 for t in tlist:
524 if not t.is_derived():
525 t.builder_set(self)
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.
540 t.builder_set(None)
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)
549 return tlist, slist
551 def _execute(self, env, target, source, overwarn={}, executor_kw={}):
552 # We now assume that target and source are lists or None.
553 if self.src_builder:
554 source = self.src_builder_sources(env, source, overwarn)
556 if self.single_source and len(source) > 1 and target is None:
557 result = []
558 if target is None: target = [None]*len(source)
559 for tgt, src in zip(target, source):
560 if tgt is not None:
561 tgt = [tgt]
562 if src is not None:
563 src = [src]
564 result.extend(self._execute(env, tgt, src, overwarn))
565 return SCons.Node.NodeList(result)
567 overwarn.warn()
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)
574 if len(tlist) > 1:
575 for t in tlist:
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
583 # sources.
585 executor = None
586 key = None
588 if self.multi:
589 try:
590 executor = tlist[0].get_executor(create = 0)
591 except (AttributeError, IndexError):
592 pass
593 else:
594 executor.add_sources(slist)
596 if executor is None:
597 if not self.action:
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)
602 if key:
603 try:
604 executor = SCons.Executor.GetBatchExecutor(key)
605 except KeyError:
606 pass
607 else:
608 executor.add_batch(tlist, slist)
610 if executor is None:
611 executor = SCons.Executor.Executor(self.action, env, [],
612 tlist, slist, executor_kw)
613 if key:
614 SCons.Executor.AddBatchExecutor(key, executor)
616 # Now set up the relevant information in the target Nodes themselves.
617 for t in tlist:
618 t.cwd = env.fs.getcwd()
619 t.builder_set(self)
620 t.env_set(env)
621 t.add_source(slist)
622 t.set_executor(executor)
623 t.set_explicit(self.is_explicit)
625 if env.get("SCONF_NODE"):
626 for node in tlist:
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.
635 if chdir is _null:
636 ekw = self.executor_kw
637 else:
638 ekw = self.executor_kw.copy()
639 ekw['chdir'] = chdir
640 if 'chdir' in ekw and SCons.Util.is_String(ekw['chdir']):
641 ekw['chdir'] = env.subst(ekw['chdir'])
642 if kw:
643 if 'srcdir' in kw:
644 def prependDirIfRelative(f, srcdir=kw['srcdir']):
645 import os.path
646 if SCons.Util.is_String(f) and not os.path.isabs(f):
647 f = os.path.join(srcdir, f)
648 return f
649 if not SCons.Util.is_List(source):
650 source = [source]
651 source = list(map(prependDirIfRelative, source))
652 del kw['srcdir']
653 if self.overrides:
654 env_kw = self.overrides.copy()
655 env_kw.update(kw)
656 else:
657 env_kw = kw
658 else:
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 [ '.', '_', '$' ]:
667 return '.' + suff
668 return suff
670 def get_prefix(self, env, sources=[]):
671 prefix = self.prefix
672 if callable(prefix):
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)
679 self.suffix = suffix
681 def get_suffix(self, env, sources=[]):
682 suffix = self.suffix
683 if callable(suffix):
684 suffix = suffix(env, sources)
685 return env.subst(suffix)
687 def set_src_suffix(self, src_suffix) -> None:
688 if not src_suffix:
689 src_suffix = []
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)
697 if not ret:
698 return ''
699 return ret[0]
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.
718 self._memo = {}
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
729 environment.
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.
741 sdict = {}
742 for bld in self.get_src_builders(env):
743 for suf in bld.src_suffixes(env):
744 sdict[suf] = bld
745 return sdict
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:
758 return suf
759 return None
761 result = []
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]
768 else:
769 match_suffix = match_src_suffix(s.name)
770 if match_suffix:
771 try:
772 bld = sdict[match_suffix]
773 except KeyError:
774 result.append(s)
775 else:
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.
780 if len(tlist) > 1:
781 tlist = [t for t in tlist if match_src_suffix(t.name)]
782 result.extend(tlist)
783 else:
784 result.append(s)
786 source_factory = env.get_factory(self.source_factory)
788 return env.arg2nodes(result, source_factory)
790 def _get_src_builders_key(self, env):
791 return id(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.
802 memo_key = id(env)
803 try:
804 memo_dict = self._memo['get_src_builders']
805 except KeyError:
806 memo_dict = {}
807 self._memo['get_src_builders'] = memo_dict
808 else:
809 try:
810 return memo_dict[memo_key]
811 except KeyError:
812 pass
814 builders = []
815 for bld in self.src_builder:
816 if SCons.Util.is_String(bld):
817 try:
818 bld = env['BUILDERS'][bld]
819 except KeyError:
820 continue
821 builders.append(bld)
823 memo_dict[memo_key] = builders
824 return builders
826 def _subst_src_suffixes_key(self, env):
827 return id(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
835 environment.
837 memo_key = id(env)
838 try:
839 memo_dict = self._memo['subst_src_suffixes']
840 except KeyError:
841 memo_dict = {}
842 self._memo['subst_src_suffixes'] = memo_dict
843 else:
844 try:
845 return memo_dict[memo_key]
846 except KeyError:
847 pass
848 suffixes = [env.subst(x) for x in self.src_suffix]
849 memo_dict[memo_key] = suffixes
850 return suffixes
852 def src_suffixes(self, env):
854 Returns the list of source suffixes for all src_builders of this
855 Builder.
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.)
861 sdict = {}
862 suffixes = self.subst_src_suffixes(env)
863 for s in suffixes:
864 sdict[s] = 1
865 for builder in self.get_src_builders(env):
866 for s in builder.src_suffixes(env):
867 if s not in sdict:
868 sdict[s] = 1
869 suffixes.append(s)
870 return suffixes
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.
883 self.cmdgen = cmdgen
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)
900 or callable(obj))
902 # Local Variables:
903 # tab-width:4
904 # indent-tabs-mode:nil
905 # End:
906 # vim: set expandtab tabstop=4 shiftwidth=4: