1""" 2SCons.Builder 3 4Builder object subsystem. 5 6A Builder object is a callable that encapsulates information about how 7to execute actions to create a target Node (file) from source Nodes 8(files), and how to create those dependencies for tracking. 9 10The main entry point here is the Builder() factory method. This provides 11a procedural interface that creates the right underlying Builder object 12based on the keyword arguments supplied and the types of the arguments. 13 14The goal is for this external interface to be simple enough that the 15vast majority of users can create new Builders as necessary to support 16building new types of files in their configurations, without having to 17dive any deeper into this subsystem. 18 19The base class here is BuilderBase. This is a concrete base class which 20does, in fact, represent the Builder objects that we (or users) create. 21 22There is also a proxy that looks like a Builder: 23 24 CompositeBuilder 25 26 This proxies for a Builder with an action that is actually a 27 dictionary that knows how to map file suffixes to a specific 28 action. This is so that we can invoke different actions 29 (compilers, compile options) for different flavors of source 30 files. 31 32Builders and their proxies have the following public interface methods 33used by other modules: 34 35 - __call__() 36 THE public interface. Calling a Builder object (with the 37 use of internal helper methods) sets up the target and source 38 dependencies, appropriate mapping to a specific action, and the 39 environment manipulation necessary for overridden construction 40 variable. This also takes care of warning about possible mistakes 41 in keyword arguments. 42 43 - add_emitter() 44 Adds an emitter for a specific file suffix, used by some Tool 45 modules to specify that (for example) a yacc invocation on a .y 46 can create a .h *and* a .c file. 47 48 - add_action() 49 Adds an action for a specific file suffix, heavily used by 50 Tool modules to add their specific action(s) for turning 51 a source file into an object file to the global static 52 and shared object file Builders. 53 54There are the following methods for internal use within this module: 55 56 - _execute() 57 The internal method that handles the heavily lifting when a 58 Builder is called. This is used so that the __call__() methods 59 can set up warning about possible mistakes in keyword-argument 60 overrides, and *then* execute all of the steps necessary so that 61 the warnings only occur once. 62 63 - get_name() 64 Returns the Builder's name within a specific Environment, 65 primarily used to try to return helpful information in error 66 messages. 67 68 - adjust_suffix() 69 - get_prefix() 70 - get_suffix() 71 - get_src_suffix() 72 - set_src_suffix() 73 Miscellaneous stuff for handling the prefix and suffix 74 manipulation we use in turning source file names into target 75 file names. 76 77""" 78 79# 80# Copyright (c) 2001 - 2019 The SCons Foundation 81# 82# Permission is hereby granted, free of charge, to any person obtaining 83# a copy of this software and associated documentation files (the 84# "Software"), to deal in the Software without restriction, including 85# without limitation the rights to use, copy, modify, merge, publish, 86# distribute, sublicense, and/or sell copies of the Software, and to 87# permit persons to whom the Software is furnished to do so, subject to 88# the following conditions: 89# 90# The above copyright notice and this permission notice shall be included 91# in all copies or substantial portions of the Software. 92# 93# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 94# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 95# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 96# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 97# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 98# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 99# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 100 101__revision__ = "src/engine/SCons/Builder.py bee7caf9defd6e108fc2998a2520ddb36a967691 2019-12-17 02:07:09 bdeegan" 102 103import collections 104 105import SCons.Action 106import SCons.Debug 107from SCons.Debug import logInstanceCreation 108from SCons.Errors import InternalError, UserError 109import SCons.Executor 110import SCons.Memoize 111import SCons.Util 112import SCons.Warnings 113 114class _Null(object): 115 pass 116 117_null = _Null 118 119def match_splitext(path, suffixes = []): 120 if suffixes: 121 matchsuf = [S for S in suffixes if path[-len(S):] == S] 122 if matchsuf: 123 suf = max([(len(_f),_f) for _f in matchsuf])[1] 124 return [path[:-len(suf)], path[-len(suf):]] 125 return SCons.Util.splitext(path) 126 127class DictCmdGenerator(SCons.Util.Selector): 128 """This is a callable class that can be used as a 129 command generator function. It holds on to a dictionary 130 mapping file suffixes to Actions. It uses that dictionary 131 to return the proper action based on the file suffix of 132 the source file.""" 133 134 def __init__(self, dict=None, source_ext_match=1): 135 SCons.Util.Selector.__init__(self, dict) 136 self.source_ext_match = source_ext_match 137 138 def src_suffixes(self): 139 return list(self.keys()) 140 141 def add_action(self, suffix, action): 142 """Add a suffix-action pair to the mapping. 143 """ 144 self[suffix] = action 145 146 def __call__(self, target, source, env, for_signature): 147 if not source: 148 return [] 149 150 if self.source_ext_match: 151 suffixes = self.src_suffixes() 152 ext = None 153 for src in map(str, source): 154 my_ext = match_splitext(src, suffixes)[1] 155 if ext and my_ext != ext: 156 raise UserError("While building `%s' from `%s': Cannot build multiple sources with different extensions: %s, %s" 157 % (repr(list(map(str, target))), src, ext, my_ext)) 158 ext = my_ext 159 else: 160 ext = match_splitext(str(source[0]), self.src_suffixes())[1] 161 162 if not ext: 163 #return ext 164 raise UserError("While building `%s': " 165 "Cannot deduce file extension from source files: %s" 166 % (repr(list(map(str, target))), repr(list(map(str, source))))) 167 168 try: 169 ret = SCons.Util.Selector.__call__(self, env, source, ext) 170 except KeyError as e: 171 raise UserError("Ambiguous suffixes after environment substitution: %s == %s == %s" % (e.args[0], e.args[1], e.args[2])) 172 if ret is None: 173 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." % \ 174 (repr(list(map(str, target))), repr(list(map(str, source))), ext, repr(list(self.keys())))) 175 return ret 176 177class CallableSelector(SCons.Util.Selector): 178 """A callable dictionary that will, in turn, call the value it 179 finds if it can.""" 180 def __call__(self, env, source): 181 value = SCons.Util.Selector.__call__(self, env, source) 182 if callable(value): 183 value = value(env, source) 184 return value 185 186class DictEmitter(SCons.Util.Selector): 187 """A callable dictionary that maps file suffixes to emitters. 188 When called, it finds the right emitter in its dictionary for the 189 suffix of the first source file, and calls that emitter to get the 190 right lists of targets and sources to return. If there's no emitter 191 for the suffix in its dictionary, the original target and source are 192 returned. 193 """ 194 def __call__(self, target, source, env): 195 emitter = SCons.Util.Selector.__call__(self, env, source) 196 if emitter: 197 target, source = emitter(target, source, env) 198 return (target, source) 199 200class ListEmitter(collections.UserList): 201 """A callable list of emitters that calls each in sequence, 202 returning the result. 203 """ 204 def __call__(self, target, source, env): 205 for e in self.data: 206 target, source = e(target, source, env) 207 return (target, source) 208 209# These are a common errors when calling a Builder; 210# they are similar to the 'target' and 'source' keyword args to builders, 211# so we issue warnings when we see them. The warnings can, of course, 212# be disabled. 213misleading_keywords = { 214 'targets' : 'target', 215 'sources' : 'source', 216} 217 218class OverrideWarner(collections.UserDict): 219 """A class for warning about keyword arguments that we use as 220 overrides in a Builder call. 221 222 This class exists to handle the fact that a single Builder call 223 can actually invoke multiple builders. This class only emits the 224 warnings once, no matter how many Builders are invoked. 225 """ 226 def __init__(self, dict): 227 collections.UserDict.__init__(self, dict) 228 if SCons.Debug.track_instances: logInstanceCreation(self, 'Builder.OverrideWarner') 229 self.already_warned = None 230 def warn(self): 231 if self.already_warned: 232 return 233 for k in list(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 239 240def 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', 1) 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']) 258 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) 274 275 result = BuilderBase(**kw) 276 277 if composite is not None: 278 result = CompositeBuilder(result, composite) 279 280 return result 281 282def _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. 286 """ 287 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) 305 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) 326 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)))) 330 331class EmitterProxy(object): 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): 339 self.var = SCons.Util.to_String(var) 340 341 def __call__(self, target, source, env): 342 emitter = self.var 343 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) 354 355 return (target, source) 356 357 358 def __eq__(self, other): 359 return self.var == other.var 360 361 def __lt__(self, other): 362 return self.var < other.var 363 364class BuilderBase(object): 365 """Base class for Builders, objects that create output 366 nodes (files) from input nodes (files). 367 """ 368 369 def __init__(self, action = None, 370 prefix = '', 371 suffix = '', 372 src_suffix = '', 373 target_factory = None, 374 source_factory = None, 375 target_scanner = None, 376 source_scanner = None, 377 emitter = None, 378 multi = 0, 379 env = None, 380 single_source = 0, 381 name = None, 382 chdir = _null, 383 is_explicit = 1, 384 src_builder = None, 385 ensure_suffix = False, 386 **overrides): 387 if SCons.Debug.track_instances: logInstanceCreation(self, 'Builder.BuilderBase') 388 self._memo = {} 389 self.action = action 390 self.multi = multi 391 if SCons.Util.is_Dict(prefix): 392 prefix = CallableSelector(prefix) 393 self.prefix = prefix 394 if SCons.Util.is_Dict(suffix): 395 suffix = CallableSelector(suffix) 396 self.env = env 397 self.single_source = single_source 398 if 'overrides' in overrides: 399 msg = "The \"overrides\" keyword to Builder() creation has been removed;\n" +\ 400 "\tspecify the items as keyword arguments to the Builder() call instead." 401 raise TypeError(msg) 402 if 'scanner' in overrides: 403 msg = "The \"scanner\" keyword to Builder() creation has been removed;\n" +\ 404 "\tuse: source_scanner or target_scanner as appropriate." 405 raise TypeError(msg) 406 self.overrides = overrides 407 408 self.set_suffix(suffix) 409 self.set_src_suffix(src_suffix) 410 self.ensure_suffix = ensure_suffix 411 412 self.target_factory = target_factory 413 self.source_factory = source_factory 414 self.target_scanner = target_scanner 415 self.source_scanner = source_scanner 416 417 self.emitter = emitter 418 419 # Optional Builder name should only be used for Builders 420 # that don't get attached to construction environments. 421 if name: 422 self.name = name 423 self.executor_kw = {} 424 if chdir is not _null: 425 self.executor_kw['chdir'] = chdir 426 self.is_explicit = is_explicit 427 428 if src_builder is None: 429 src_builder = [] 430 elif not SCons.Util.is_List(src_builder): 431 src_builder = [ src_builder ] 432 self.src_builder = src_builder 433 434 def __nonzero__(self): 435 raise InternalError("Do not test for the Node.builder attribute directly; use Node.has_builder() instead") 436 437 def __bool__(self): 438 return self.__nonzero__() 439 440 def get_name(self, env): 441 """Attempts to get the name of the Builder. 442 443 Look at the BUILDERS variable of env, expecting it to be a 444 dictionary containing this Builder, and return the key of the 445 dictionary. If there's no key, then return a directly-configured 446 name (if there is one) or the name of the class (by default).""" 447 448 try: 449 index = list(env['BUILDERS'].values()).index(self) 450 return list(env['BUILDERS'].keys())[index] 451 except (AttributeError, KeyError, TypeError, ValueError): 452 try: 453 return self.name 454 except AttributeError: 455 return str(self.__class__) 456 457 def __eq__(self, other): 458 return self.__dict__ == other.__dict__ 459 460 def splitext(self, path, env=None): 461 if not env: 462 env = self.env 463 if env: 464 suffixes = self.src_suffixes(env) 465 else: 466 suffixes = [] 467 return match_splitext(path, suffixes) 468 469 def _adjustixes(self, files, pre, suf, ensure_suffix=False): 470 if not files: 471 return [] 472 result = [] 473 if not SCons.Util.is_List(files): 474 files = [files] 475 476 for f in files: 477 if SCons.Util.is_String(f): 478 f = SCons.Util.adjustixes(f, pre, suf, ensure_suffix) 479 result.append(f) 480 return result 481 482 def _create_nodes(self, env, target = None, source = None): 483 """Create and return lists of target and source nodes. 484 """ 485 src_suf = self.get_src_suffix(env) 486 487 target_factory = env.get_factory(self.target_factory) 488 source_factory = env.get_factory(self.source_factory) 489 490 source = self._adjustixes(source, None, src_suf) 491 slist = env.arg2nodes(source, source_factory) 492 493 pre = self.get_prefix(env, slist) 494 suf = self.get_suffix(env, slist) 495 496 if target is None: 497 try: 498 t_from_s = slist[0].target_from_source 499 except AttributeError: 500 raise UserError("Do not know how to create a target from source `%s'" % slist[0]) 501 except IndexError: 502 tlist = [] 503 else: 504 splitext = lambda S: self.splitext(S,env) 505 tlist = [ t_from_s(pre, suf, splitext) ] 506 else: 507 target = self._adjustixes(target, pre, suf, self.ensure_suffix) 508 tlist = env.arg2nodes(target, target_factory, target=target, source=source) 509 510 if self.emitter: 511 # The emitter is going to do str(node), but because we're 512 # being called *from* a builder invocation, the new targets 513 # don't yet have a builder set on them and will look like 514 # source files. Fool the emitter's str() calls by setting 515 # up a temporary builder on the new targets. 516 new_targets = [] 517 for t in tlist: 518 if not t.is_derived(): 519 t.builder_set(self) 520 new_targets.append(t) 521 522 orig_tlist = tlist[:] 523 orig_slist = slist[:] 524 525 target, source = self.emitter(target=tlist, source=slist, env=env) 526 527 # Now delete the temporary builders that we attached to any 528 # new targets, so that _node_errors() doesn't do weird stuff 529 # to them because it thinks they already have builders. 530 for t in new_targets: 531 if t.builder is self: 532 # Only delete the temporary builder if the emitter 533 # didn't change it on us. 534 t.builder_set(None) 535 536 # Have to call arg2nodes yet again, since it is legal for 537 # emitters to spit out strings as well as Node instances. 538 tlist = env.arg2nodes(target, target_factory, 539 target=orig_tlist, source=orig_slist) 540 slist = env.arg2nodes(source, source_factory, 541 target=orig_tlist, source=orig_slist) 542 543 return tlist, slist 544 545 def _execute(self, env, target, source, overwarn={}, executor_kw={}): 546 # We now assume that target and source are lists or None. 547 if self.src_builder: 548 source = self.src_builder_sources(env, source, overwarn) 549 550 if self.single_source and len(source) > 1 and target is None: 551 result = [] 552 if target is None: target = [None]*len(source) 553 for tgt, src in zip(target, source): 554 if tgt is not None: 555 tgt = [tgt] 556 if src is not None: 557 src = [src] 558 result.extend(self._execute(env, tgt, src, overwarn)) 559 return SCons.Node.NodeList(result) 560 561 overwarn.warn() 562 563 tlist, slist = self._create_nodes(env, target, source) 564 565 # If there is more than one target ensure that if we need to reset 566 # the implicit list to new scan of dependency all targets implicit lists 567 # are cleared. (SCons GH Issue #2811 and MongoDB SERVER-33111) 568 if len(tlist) > 1: 569 for t in tlist: 570 t.target_peers = tlist 571 572 # Check for errors with the specified target/source lists. 573 _node_errors(self, env, tlist, slist) 574 575 # The targets are fine, so find or make the appropriate Executor to 576 # build this particular list of targets from this particular list of 577 # sources. 578 579 executor = None 580 key = None 581 582 if self.multi: 583 try: 584 executor = tlist[0].get_executor(create = 0) 585 except (AttributeError, IndexError): 586 pass 587 else: 588 executor.add_sources(slist) 589 590 if executor is None: 591 if not self.action: 592 fmt = "Builder %s must have an action to build %s." 593 raise UserError(fmt % (self.get_name(env or self.env), 594 list(map(str,tlist)))) 595 key = self.action.batch_key(env or self.env, tlist, slist) 596 if key: 597 try: 598 executor = SCons.Executor.GetBatchExecutor(key) 599 except KeyError: 600 pass 601 else: 602 executor.add_batch(tlist, slist) 603 604 if executor is None: 605 executor = SCons.Executor.Executor(self.action, env, [], 606 tlist, slist, executor_kw) 607 if key: 608 SCons.Executor.AddBatchExecutor(key, executor) 609 610 # Now set up the relevant information in the target Nodes themselves. 611 for t in tlist: 612 t.cwd = env.fs.getcwd() 613 t.builder_set(self) 614 t.env_set(env) 615 t.add_source(slist) 616 t.set_executor(executor) 617 t.set_explicit(self.is_explicit) 618 619 return SCons.Node.NodeList(tlist) 620 621 def __call__(self, env, target=None, source=None, chdir=_null, **kw): 622 # We now assume that target and source are lists or None. 623 # The caller (typically Environment.BuilderWrapper) is 624 # responsible for converting any scalar values to lists. 625 if chdir is _null: 626 ekw = self.executor_kw 627 else: 628 ekw = self.executor_kw.copy() 629 ekw['chdir'] = chdir 630 if 'chdir' in ekw and SCons.Util.is_String(ekw['chdir']): 631 ekw['chdir'] = env.subst(ekw['chdir']) 632 if kw: 633 if 'srcdir' in kw: 634 def prependDirIfRelative(f, srcdir=kw['srcdir']): 635 import os.path 636 if SCons.Util.is_String(f) and not os.path.isabs(f): 637 f = os.path.join(srcdir, f) 638 return f 639 if not SCons.Util.is_List(source): 640 source = [source] 641 source = list(map(prependDirIfRelative, source)) 642 del kw['srcdir'] 643 if self.overrides: 644 env_kw = self.overrides.copy() 645 env_kw.update(kw) 646 else: 647 env_kw = kw 648 else: 649 env_kw = self.overrides 650 env = env.Override(env_kw) 651 return self._execute(env, target, source, OverrideWarner(kw), ekw) 652 653 def adjust_suffix(self, suff): 654 if suff and not suff[0] in [ '.', '_', '$' ]: 655 return '.' + suff 656 return suff 657 658 def get_prefix(self, env, sources=[]): 659 prefix = self.prefix 660 if callable(prefix): 661 prefix = prefix(env, sources) 662 return env.subst(prefix) 663 664 def set_suffix(self, suffix): 665 if not callable(suffix): 666 suffix = self.adjust_suffix(suffix) 667 self.suffix = suffix 668 669 def get_suffix(self, env, sources=[]): 670 suffix = self.suffix 671 if callable(suffix): 672 suffix = suffix(env, sources) 673 return env.subst(suffix) 674 675 def set_src_suffix(self, src_suffix): 676 if not src_suffix: 677 src_suffix = [] 678 elif not SCons.Util.is_List(src_suffix): 679 src_suffix = [ src_suffix ] 680 self.src_suffix = [callable(suf) and suf or self.adjust_suffix(suf) for suf in src_suffix] 681 682 def get_src_suffix(self, env): 683 """Get the first src_suffix in the list of src_suffixes.""" 684 ret = self.src_suffixes(env) 685 if not ret: 686 return '' 687 return ret[0] 688 689 def add_emitter(self, suffix, emitter): 690 """Add a suffix-emitter mapping to this Builder. 691 692 This assumes that emitter has been initialized with an 693 appropriate dictionary type, and will throw a TypeError if 694 not, so the caller is responsible for knowing that this is an 695 appropriate method to call for the Builder in question. 696 """ 697 self.emitter[suffix] = emitter 698 699 def add_src_builder(self, builder): 700 """ 701 Add a new Builder to the list of src_builders. 702 703 This requires wiping out cached values so that the computed 704 lists of source suffixes get re-calculated. 705 """ 706 self._memo = {} 707 self.src_builder.append(builder) 708 709 def _get_sdict(self, env): 710 """ 711 Returns a dictionary mapping all of the source suffixes of all 712 src_builders of this Builder to the underlying Builder that 713 should be called first. 714 715 This dictionary is used for each target specified, so we save a 716 lot of extra computation by memoizing it for each construction 717 environment. 718 719 Note that this is re-computed each time, not cached, because there 720 might be changes to one of our source Builders (or one of their 721 source Builders, and so on, and so on...) that we can't "see." 722 723 The underlying methods we call cache their computed values, 724 though, so we hope repeatedly aggregating them into a dictionary 725 like this won't be too big a hit. We may need to look for a 726 better way to do this if performance data show this has turned 727 into a significant bottleneck. 728 """ 729 sdict = {} 730 for bld in self.get_src_builders(env): 731 for suf in bld.src_suffixes(env): 732 sdict[suf] = bld 733 return sdict 734 735 def src_builder_sources(self, env, source, overwarn={}): 736 sdict = self._get_sdict(env) 737 738 src_suffixes = self.src_suffixes(env) 739 740 lengths = list(set(map(len, src_suffixes))) 741 742 def match_src_suffix(name, src_suffixes=src_suffixes, lengths=lengths): 743 node_suffixes = [name[-l:] for l in lengths] 744 for suf in src_suffixes: 745 if suf in node_suffixes: 746 return suf 747 return None 748 749 result = [] 750 for s in SCons.Util.flatten(source): 751 if SCons.Util.is_String(s): 752 match_suffix = match_src_suffix(env.subst(s)) 753 if not match_suffix and '.' not in s: 754 src_suf = self.get_src_suffix(env) 755 s = self._adjustixes(s, None, src_suf)[0] 756 else: 757 match_suffix = match_src_suffix(s.name) 758 if match_suffix: 759 try: 760 bld = sdict[match_suffix] 761 except KeyError: 762 result.append(s) 763 else: 764 tlist = bld._execute(env, None, [s], overwarn) 765 # If the subsidiary Builder returned more than one 766 # target, then filter out any sources that this 767 # Builder isn't capable of building. 768 if len(tlist) > 1: 769 tlist = [t for t in tlist if match_src_suffix(t.name)] 770 result.extend(tlist) 771 else: 772 result.append(s) 773 774 source_factory = env.get_factory(self.source_factory) 775 776 return env.arg2nodes(result, source_factory) 777 778 def _get_src_builders_key(self, env): 779 return id(env) 780 781 @SCons.Memoize.CountDictCall(_get_src_builders_key) 782 def get_src_builders(self, env): 783 """ 784 Returns the list of source Builders for this Builder. 785 786 This exists mainly to look up Builders referenced as 787 strings in the 'BUILDER' variable of the construction 788 environment and cache the result. 789 """ 790 memo_key = id(env) 791 try: 792 memo_dict = self._memo['get_src_builders'] 793 except KeyError: 794 memo_dict = {} 795 self._memo['get_src_builders'] = memo_dict 796 else: 797 try: 798 return memo_dict[memo_key] 799 except KeyError: 800 pass 801 802 builders = [] 803 for bld in self.src_builder: 804 if SCons.Util.is_String(bld): 805 try: 806 bld = env['BUILDERS'][bld] 807 except KeyError: 808 continue 809 builders.append(bld) 810 811 memo_dict[memo_key] = builders 812 return builders 813 814 def _subst_src_suffixes_key(self, env): 815 return id(env) 816 817 @SCons.Memoize.CountDictCall(_subst_src_suffixes_key) 818 def subst_src_suffixes(self, env): 819 """ 820 The suffix list may contain construction variable expansions, 821 so we have to evaluate the individual strings. To avoid doing 822 this over and over, we memoize the results for each construction 823 environment. 824 """ 825 memo_key = id(env) 826 try: 827 memo_dict = self._memo['subst_src_suffixes'] 828 except KeyError: 829 memo_dict = {} 830 self._memo['subst_src_suffixes'] = memo_dict 831 else: 832 try: 833 return memo_dict[memo_key] 834 except KeyError: 835 pass 836 suffixes = [env.subst(x) for x in self.src_suffix] 837 memo_dict[memo_key] = suffixes 838 return suffixes 839 840 def src_suffixes(self, env): 841 """ 842 Returns the list of source suffixes for all src_builders of this 843 Builder. 844 845 This is essentially a recursive descent of the src_builder "tree." 846 (This value isn't cached because there may be changes in a 847 src_builder many levels deep that we can't see.) 848 """ 849 sdict = {} 850 suffixes = self.subst_src_suffixes(env) 851 for s in suffixes: 852 sdict[s] = 1 853 for builder in self.get_src_builders(env): 854 for s in builder.src_suffixes(env): 855 if s not in sdict: 856 sdict[s] = 1 857 suffixes.append(s) 858 return suffixes 859 860class CompositeBuilder(SCons.Util.Proxy): 861 """A Builder Proxy whose main purpose is to always have 862 a DictCmdGenerator as its action, and to provide access 863 to the DictCmdGenerator's add_action() method. 864 """ 865 866 def __init__(self, builder, cmdgen): 867 if SCons.Debug.track_instances: logInstanceCreation(self, 'Builder.CompositeBuilder') 868 SCons.Util.Proxy.__init__(self, builder) 869 870 # cmdgen should always be an instance of DictCmdGenerator. 871 self.cmdgen = cmdgen 872 self.builder = builder 873 874 __call__ = SCons.Util.Delegate('__call__') 875 876 def add_action(self, suffix, action): 877 self.cmdgen.add_action(suffix, action) 878 self.set_src_suffix(self.cmdgen.src_suffixes()) 879 880def is_a_Builder(obj): 881 """"Returns True if the specified obj is one of our Builder classes. 882 883 The test is complicated a bit by the fact that CompositeBuilder 884 is a proxy, not a subclass of BuilderBase. 885 """ 886 return (isinstance(obj, BuilderBase) 887 or isinstance(obj, CompositeBuilder) 888 or callable(obj)) 889 890# Local Variables: 891# tab-width:4 892# indent-tabs-mode:nil 893# End: 894# vim: set expandtab tabstop=4 shiftwidth=4: 895