1# MIT License 2# 3# Copyright The SCons Foundation 4# 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: 12# 13# The above copyright notice and this permission notice shall be included 14# in all copies or substantial portions of the Software. 15# 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. 23 24"""SCons Actions. 25 26Information about executing any sort of action that 27can build one or more target Nodes (typically files) from one or more 28source Nodes (also typically files) given a specific Environment. 29 30The base class here is ActionBase. The base class supplies just a few 31utility methods and some generic methods for displaying information 32about an Action in response to the various commands that control printing. 33 34A second-level base class is _ActionAction. This extends ActionBase 35by providing the methods that can be used to show and perform an 36action. True Action objects will subclass _ActionAction; Action 37factory class objects will subclass ActionBase. 38 39The heavy lifting is handled by subclasses for the different types of 40actions we might execute: 41 42 CommandAction 43 CommandGeneratorAction 44 FunctionAction 45 ListAction 46 47The subclasses supply the following public interface methods used by 48other modules: 49 50 __call__() 51 THE public interface, "calling" an Action object executes the 52 command or Python function. This also takes care of printing 53 a pre-substitution command for debugging purposes. 54 55 get_contents() 56 Fetches the "contents" of an Action for signature calculation 57 plus the varlist. This is what gets checksummed to decide 58 if a target needs to be rebuilt because its action changed. 59 60 genstring() 61 Returns a string representation of the Action *without* 62 command substitution, but allows a CommandGeneratorAction to 63 generate the right action based on the specified target, 64 source and env. This is used by the Signature subsystem 65 (through the Executor) to obtain an (imprecise) representation 66 of the Action operation for informative purposes. 67 68 69Subclasses also supply the following methods for internal use within 70this module: 71 72 __str__() 73 Returns a string approximation of the Action; no variable 74 substitution is performed. 75 76 execute() 77 The internal method that really, truly, actually handles the 78 execution of a command or Python function. This is used so 79 that the __call__() methods can take care of displaying any 80 pre-substitution representations, and *then* execute an action 81 without worrying about the specific Actions involved. 82 83 get_presig() 84 Fetches the "contents" of a subclass for signature calculation. 85 The varlist is added to this to produce the Action's contents. 86 TODO(?): Change this to always return bytes and not str? 87 88 strfunction() 89 Returns a substituted string representation of the Action. 90 This is used by the _ActionAction.show() command to display the 91 command/function that will be executed to generate the target(s). 92 93There is a related independent ActionCaller class that looks like a 94regular Action, and which serves as a wrapper for arbitrary functions 95that we want to let the user specify the arguments to now, but actually 96execute later (when an out-of-date check determines that it's needed to 97be executed, for example). Objects of this class are returned by an 98ActionFactory class that provides a __call__() method as a convenient 99way for wrapping up the functions. 100 101""" 102 103import os 104import pickle 105import re 106import sys 107import subprocess 108from subprocess import DEVNULL 109import inspect 110from collections import OrderedDict 111 112import SCons.Debug 113from SCons.Debug import logInstanceCreation 114import SCons.Errors 115import SCons.Util 116import SCons.Subst 117 118# we use these a lot, so try to optimize them 119from SCons.Util import is_String, is_List 120 121class _null: 122 pass 123 124print_actions = True 125execute_actions = True 126print_actions_presub = False 127 128# Use pickle protocol 1 when pickling functions for signature 129# otherwise python3 and python2 will yield different pickles 130# for the same object. 131# This is due to default being 1 for python 2.7, and 3 for 3.x 132# TODO: We can roll this forward to 2 (if it has value), but not 133# before a deprecation cycle as the sconsigns will change 134ACTION_SIGNATURE_PICKLE_PROTOCOL = 1 135 136 137def rfile(n): 138 try: 139 return n.rfile() 140 except AttributeError: 141 return n 142 143 144def default_exitstatfunc(s): 145 return s 146 147strip_quotes = re.compile(r'^[\'"](.*)[\'"]$') 148 149 150def _callable_contents(obj): 151 """Return the signature contents of a callable Python object. 152 """ 153 try: 154 # Test if obj is a method. 155 return _function_contents(obj.__func__) 156 157 except AttributeError: 158 try: 159 # Test if obj is a callable object. 160 return _function_contents(obj.__call__.__func__) 161 162 except AttributeError: 163 try: 164 # Test if obj is a code object. 165 return _code_contents(obj) 166 167 except AttributeError: 168 # Test if obj is a function object. 169 return _function_contents(obj) 170 171 172def _object_contents(obj): 173 """Return the signature contents of any Python object. 174 175 We have to handle the case where object contains a code object 176 since it can be pickled directly. 177 """ 178 try: 179 # Test if obj is a method. 180 return _function_contents(obj.__func__) 181 182 except AttributeError: 183 try: 184 # Test if obj is a callable object. 185 return _function_contents(obj.__call__.__func__) 186 187 except AttributeError: 188 try: 189 # Test if obj is a code object. 190 return _code_contents(obj) 191 192 except AttributeError: 193 try: 194 # Test if obj is a function object. 195 return _function_contents(obj) 196 197 except AttributeError as ae: 198 # Should be a pickle-able Python object. 199 try: 200 return _object_instance_content(obj) 201 # pickling an Action instance or object doesn't yield a stable 202 # content as instance property may be dumped in different orders 203 # return pickle.dumps(obj, ACTION_SIGNATURE_PICKLE_PROTOCOL) 204 except (pickle.PicklingError, TypeError, AttributeError) as ex: 205 # This is weird, but it seems that nested classes 206 # are unpickable. The Python docs say it should 207 # always be a PicklingError, but some Python 208 # versions seem to return TypeError. Just do 209 # the best we can. 210 return bytearray(repr(obj), 'utf-8') 211 212 213def _code_contents(code, docstring=None): 214 r"""Return the signature contents of a code object. 215 216 By providing direct access to the code object of the 217 function, Python makes this extremely easy. Hooray! 218 219 Unfortunately, older versions of Python include line 220 number indications in the compiled byte code. Boo! 221 So we remove the line number byte codes to prevent 222 recompilations from moving a Python function. 223 224 See: 225 - https://docs.python.org/2/library/inspect.html 226 - http://python-reference.readthedocs.io/en/latest/docs/code/index.html 227 228 For info on what each co\_ variable provides 229 230 The signature is as follows (should be byte/chars): 231 co_argcount, len(co_varnames), len(co_cellvars), len(co_freevars), 232 ( comma separated signature for each object in co_consts ), 233 ( comma separated signature for each object in co_names ), 234 ( The bytecode with line number bytecodes removed from co_code ) 235 236 co_argcount - Returns the number of positional arguments (including arguments with default values). 237 co_varnames - Returns a tuple containing the names of the local variables (starting with the argument names). 238 co_cellvars - Returns a tuple containing the names of local variables that are referenced by nested functions. 239 co_freevars - Returns a tuple containing the names of free variables. (?) 240 co_consts - Returns a tuple containing the literals used by the bytecode. 241 co_names - Returns a tuple containing the names used by the bytecode. 242 co_code - Returns a string representing the sequence of bytecode instructions. 243 244 """ 245 246 # contents = [] 247 248 # The code contents depends on the number of local variables 249 # but not their actual names. 250 contents = bytearray("{}, {}".format(code.co_argcount, len(code.co_varnames)), 'utf-8') 251 252 contents.extend(b", ") 253 contents.extend(bytearray(str(len(code.co_cellvars)), 'utf-8')) 254 contents.extend(b", ") 255 contents.extend(bytearray(str(len(code.co_freevars)), 'utf-8')) 256 257 # The code contents depends on any constants accessed by the 258 # function. Note that we have to call _object_contents on each 259 # constants because the code object of nested functions can 260 # show-up among the constants. 261 z = [_object_contents(cc) for cc in code.co_consts if cc != docstring] 262 contents.extend(b',(') 263 contents.extend(bytearray(',', 'utf-8').join(z)) 264 contents.extend(b')') 265 266 # The code contents depends on the variable names used to 267 # accessed global variable, as changing the variable name changes 268 # the variable actually accessed and therefore changes the 269 # function result. 270 z= [bytearray(_object_contents(cc)) for cc in code.co_names] 271 contents.extend(b',(') 272 contents.extend(bytearray(',','utf-8').join(z)) 273 contents.extend(b')') 274 275 # The code contents depends on its actual code!!! 276 contents.extend(b',(') 277 contents.extend(code.co_code) 278 contents.extend(b')') 279 280 return contents 281 282 283def _function_contents(func): 284 """ 285 The signature is as follows (should be byte/chars): 286 < _code_contents (see above) from func.__code__ > 287 ,( comma separated _object_contents for function argument defaults) 288 ,( comma separated _object_contents for any closure contents ) 289 290 291 See also: https://docs.python.org/3/reference/datamodel.html 292 - func.__code__ - The code object representing the compiled function body. 293 - func.__defaults__ - A tuple containing default argument values for those arguments that have defaults, or None if no arguments have a default value 294 - func.__closure__ - None or a tuple of cells that contain bindings for the function's free variables. 295 296 :Returns: 297 Signature contents of a function. (in bytes) 298 """ 299 300 contents = [_code_contents(func.__code__, func.__doc__)] 301 302 # The function contents depends on the value of defaults arguments 303 if func.__defaults__: 304 305 function_defaults_contents = [_object_contents(cc) for cc in func.__defaults__] 306 307 defaults = bytearray(b',(') 308 defaults.extend(bytearray(b',').join(function_defaults_contents)) 309 defaults.extend(b')') 310 311 contents.append(defaults) 312 else: 313 contents.append(b',()') 314 315 # The function contents depends on the closure captured cell values. 316 closure = func.__closure__ or [] 317 318 try: 319 closure_contents = [_object_contents(x.cell_contents) for x in closure] 320 except AttributeError: 321 closure_contents = [] 322 323 contents.append(b',(') 324 contents.append(bytearray(b',').join(closure_contents)) 325 contents.append(b')') 326 327 retval = bytearray(b'').join(contents) 328 return retval 329 330 331def _object_instance_content(obj): 332 """ 333 Returns consistant content for a action class or an instance thereof 334 335 :Parameters: 336 - `obj` Should be either and action class or an instance thereof 337 338 :Returns: 339 bytearray or bytes representing the obj suitable for generating a signature from. 340 """ 341 retval = bytearray() 342 343 if obj is None: 344 return b'N.' 345 346 if isinstance(obj, SCons.Util.BaseStringTypes): 347 return SCons.Util.to_bytes(obj) 348 349 inst_class = obj.__class__ 350 inst_class_name = bytearray(obj.__class__.__name__,'utf-8') 351 inst_class_module = bytearray(obj.__class__.__module__,'utf-8') 352 inst_class_hierarchy = bytearray(repr(inspect.getclasstree([obj.__class__,])),'utf-8') 353 # print("ICH:%s : %s"%(inst_class_hierarchy, repr(obj))) 354 355 properties = [(p, getattr(obj, p, "None")) for p in dir(obj) if not (p[:2] == '__' or inspect.ismethod(getattr(obj, p)) or inspect.isbuiltin(getattr(obj,p))) ] 356 properties.sort() 357 properties_str = ','.join(["%s=%s"%(p[0],p[1]) for p in properties]) 358 properties_bytes = bytearray(properties_str,'utf-8') 359 360 methods = [p for p in dir(obj) if inspect.ismethod(getattr(obj, p))] 361 methods.sort() 362 363 method_contents = [] 364 for m in methods: 365 # print("Method:%s"%m) 366 v = _function_contents(getattr(obj, m)) 367 # print("[%s->]V:%s [%s]"%(m,v,type(v))) 368 method_contents.append(v) 369 370 retval = bytearray(b'{') 371 retval.extend(inst_class_name) 372 retval.extend(b":") 373 retval.extend(inst_class_module) 374 retval.extend(b'}[[') 375 retval.extend(inst_class_hierarchy) 376 retval.extend(b']]{{') 377 retval.extend(bytearray(b",").join(method_contents)) 378 retval.extend(b"}}{{{") 379 retval.extend(properties_bytes) 380 retval.extend(b'}}}') 381 return retval 382 383 # print("class :%s"%inst_class) 384 # print("class_name :%s"%inst_class_name) 385 # print("class_module :%s"%inst_class_module) 386 # print("Class hier :\n%s"%pp.pformat(inst_class_hierarchy)) 387 # print("Inst Properties:\n%s"%pp.pformat(properties)) 388 # print("Inst Methods :\n%s"%pp.pformat(methods)) 389 390def _actionAppend(act1, act2): 391 # This function knows how to slap two actions together. 392 # Mainly, it handles ListActions by concatenating into 393 # a single ListAction. 394 a1 = Action(act1) 395 a2 = Action(act2) 396 if a1 is None: 397 return a2 398 if a2 is None: 399 return a1 400 if isinstance(a1, ListAction): 401 if isinstance(a2, ListAction): 402 return ListAction(a1.list + a2.list) 403 else: 404 return ListAction(a1.list + [ a2 ]) 405 else: 406 if isinstance(a2, ListAction): 407 return ListAction([ a1 ] + a2.list) 408 else: 409 return ListAction([ a1, a2 ]) 410 411 412def _do_create_keywords(args, kw): 413 """This converts any arguments after the action argument into 414 their equivalent keywords and adds them to the kw argument. 415 """ 416 v = kw.get('varlist', ()) 417 # prevent varlist="FOO" from being interpreted as ['F', 'O', 'O'] 418 if is_String(v): v = (v,) 419 kw['varlist'] = tuple(v) 420 if args: 421 # turn positional args into equivalent keywords 422 cmdstrfunc = args[0] 423 if cmdstrfunc is None or is_String(cmdstrfunc): 424 kw['cmdstr'] = cmdstrfunc 425 elif callable(cmdstrfunc): 426 kw['strfunction'] = cmdstrfunc 427 else: 428 raise SCons.Errors.UserError( 429 'Invalid command display variable type. ' 430 'You must either pass a string or a callback which ' 431 'accepts (target, source, env) as parameters.') 432 if len(args) > 1: 433 kw['varlist'] = tuple(SCons.Util.flatten(args[1:])) + kw['varlist'] 434 if kw.get('strfunction', _null) is not _null \ 435 and kw.get('cmdstr', _null) is not _null: 436 raise SCons.Errors.UserError( 437 'Cannot have both strfunction and cmdstr args to Action()') 438 439 440def _do_create_action(act, kw): 441 """This is the actual "implementation" for the 442 Action factory method, below. This handles the 443 fact that passing lists to Action() itself has 444 different semantics than passing lists as elements 445 of lists. 446 447 The former will create a ListAction, the latter 448 will create a CommandAction by converting the inner 449 list elements to strings.""" 450 451 if isinstance(act, ActionBase): 452 return act 453 454 if is_String(act): 455 var=SCons.Util.get_environment_var(act) 456 if var: 457 # This looks like a string that is purely an Environment 458 # variable reference, like "$FOO" or "${FOO}". We do 459 # something special here...we lazily evaluate the contents 460 # of that Environment variable, so a user could put something 461 # like a function or a CommandGenerator in that variable 462 # instead of a string. 463 return LazyAction(var, kw) 464 commands = str(act).split('\n') 465 if len(commands) == 1: 466 return CommandAction(commands[0], **kw) 467 # The list of string commands may include a LazyAction, so we 468 # reprocess them via _do_create_list_action. 469 return _do_create_list_action(commands, kw) 470 471 if is_List(act): 472 return CommandAction(act, **kw) 473 474 if callable(act): 475 try: 476 gen = kw['generator'] 477 del kw['generator'] 478 except KeyError: 479 gen = 0 480 if gen: 481 action_type = CommandGeneratorAction 482 else: 483 action_type = FunctionAction 484 return action_type(act, kw) 485 486 # Catch a common error case with a nice message: 487 if isinstance(act, int) or isinstance(act, float): 488 raise TypeError("Don't know how to create an Action from a number (%s)"%act) 489 # Else fail silently (???) 490 return None 491 492 493def _do_create_list_action(act, kw): 494 """A factory for list actions. Convert the input list into Actions 495 and then wrap them in a ListAction.""" 496 acts = [] 497 for a in act: 498 aa = _do_create_action(a, kw) 499 if aa is not None: acts.append(aa) 500 if not acts: 501 return ListAction([]) 502 elif len(acts) == 1: 503 return acts[0] 504 else: 505 return ListAction(acts) 506 507 508def Action(act, *args, **kw): 509 """A factory for action objects.""" 510 # Really simple: the _do_create_* routines do the heavy lifting. 511 _do_create_keywords(args, kw) 512 if is_List(act): 513 return _do_create_list_action(act, kw) 514 return _do_create_action(act, kw) 515 516 517class ActionBase: 518 """Base class for all types of action objects that can be held by 519 other objects (Builders, Executors, etc.) This provides the 520 common methods for manipulating and combining those actions.""" 521 522 def __eq__(self, other): 523 return self.__dict__ == other 524 525 def no_batch_key(self, env, target, source): 526 return None 527 528 batch_key = no_batch_key 529 530 def genstring(self, target, source, env): 531 return str(self) 532 533 def get_contents(self, target, source, env): 534 result = self.get_presig(target, source, env) 535 536 if not isinstance(result,(bytes, bytearray)): 537 result = bytearray(result, 'utf-8') 538 else: 539 # Make a copy and put in bytearray, without this the contents returned by get_presig 540 # can be changed by the logic below, appending with each call and causing very 541 # hard to track down issues... 542 result = bytearray(result) 543 544 # At this point everything should be a bytearray 545 546 # This should never happen, as the Action() factory should wrap 547 # the varlist, but just in case an action is created directly, 548 # we duplicate this check here. 549 vl = self.get_varlist(target, source, env) 550 if is_String(vl): vl = (vl,) 551 for v in vl: 552 # do the subst this way to ignore $(...$) parts: 553 if isinstance(result, bytearray): 554 result.extend(SCons.Util.to_bytes(env.subst_target_source('${'+v+'}', SCons.Subst.SUBST_SIG, target, source))) 555 else: 556 raise Exception("WE SHOULD NEVER GET HERE result should be bytearray not:%s"%type(result)) 557 # result.append(SCons.Util.to_bytes(env.subst_target_source('${'+v+'}', SCons.Subst.SUBST_SIG, target, source))) 558 559 560 if isinstance(result, (bytes,bytearray)): 561 return result 562 else: 563 raise Exception("WE SHOULD NEVER GET HERE - #2 result should be bytearray not:%s" % type(result)) 564 # return b''.join(result) 565 566 def __add__(self, other): 567 return _actionAppend(self, other) 568 569 def __radd__(self, other): 570 return _actionAppend(other, self) 571 572 def presub_lines(self, env): 573 # CommandGeneratorAction needs a real environment 574 # in order to return the proper string here, since 575 # it may call LazyAction, which looks up a key 576 # in that env. So we temporarily remember the env here, 577 # and CommandGeneratorAction will use this env 578 # when it calls its _generate method. 579 self.presub_env = env 580 lines = str(self).split('\n') 581 self.presub_env = None # don't need this any more 582 return lines 583 584 def get_varlist(self, target, source, env, executor=None): 585 return self.varlist 586 587 def get_targets(self, env, executor): 588 """ 589 Returns the type of targets ($TARGETS, $CHANGED_TARGETS) used 590 by this action. 591 """ 592 return self.targets 593 594 595class _ActionAction(ActionBase): 596 """Base class for actions that create output objects.""" 597 def __init__(self, cmdstr=_null, strfunction=_null, varlist=(), 598 presub=_null, chdir=None, exitstatfunc=None, 599 batch_key=None, targets='$TARGETS', 600 **kw): 601 self.cmdstr = cmdstr 602 if strfunction is not _null: 603 if strfunction is None: 604 self.cmdstr = None 605 else: 606 self.strfunction = strfunction 607 self.varlist = varlist 608 self.presub = presub 609 self.chdir = chdir 610 if not exitstatfunc: 611 exitstatfunc = default_exitstatfunc 612 self.exitstatfunc = exitstatfunc 613 614 self.targets = targets 615 616 if batch_key: 617 if not callable(batch_key): 618 # They have set batch_key, but not to their own 619 # callable. The default behavior here will batch 620 # *all* targets+sources using this action, separated 621 # for each construction environment. 622 def default_batch_key(self, env, target, source): 623 return (id(self), id(env)) 624 batch_key = default_batch_key 625 SCons.Util.AddMethod(self, batch_key, 'batch_key') 626 627 def print_cmd_line(self, s, target, source, env): 628 """ 629 In python 3, and in some of our tests, sys.stdout is 630 a String io object, and it takes unicode strings only 631 This code assumes s is a regular string. 632 """ 633 sys.stdout.write(s + "\n") 634 635 def __call__(self, target, source, env, 636 exitstatfunc=_null, 637 presub=_null, 638 show=_null, 639 execute=_null, 640 chdir=_null, 641 executor=None): 642 if not is_List(target): 643 target = [target] 644 if not is_List(source): 645 source = [source] 646 647 if presub is _null: 648 presub = self.presub 649 if presub is _null: 650 presub = print_actions_presub 651 if exitstatfunc is _null: 652 exitstatfunc = self.exitstatfunc 653 if show is _null: 654 show = print_actions 655 if execute is _null: 656 execute = execute_actions 657 if chdir is _null: 658 chdir = self.chdir 659 save_cwd = None 660 if chdir: 661 save_cwd = os.getcwd() 662 try: 663 chdir = str(chdir.get_abspath()) 664 except AttributeError: 665 if not is_String(chdir): 666 if executor: 667 chdir = str(executor.batches[0].targets[0].dir) 668 else: 669 chdir = str(target[0].dir) 670 if presub: 671 if executor: 672 target = executor.get_all_targets() 673 source = executor.get_all_sources() 674 t = ' and '.join(map(str, target)) 675 l = '\n '.join(self.presub_lines(env)) 676 out = "Building %s with action:\n %s\n" % (t, l) 677 sys.stdout.write(out) 678 cmd = None 679 if show and self.strfunction: 680 if executor: 681 target = executor.get_all_targets() 682 source = executor.get_all_sources() 683 try: 684 cmd = self.strfunction(target, source, env, executor) 685 except TypeError: 686 cmd = self.strfunction(target, source, env) 687 if cmd: 688 if chdir: 689 cmd = ('os.chdir(%s)\n' % repr(chdir)) + cmd 690 try: 691 get = env.get 692 except AttributeError: 693 print_func = self.print_cmd_line 694 else: 695 print_func = get('PRINT_CMD_LINE_FUNC') 696 if not print_func: 697 print_func = self.print_cmd_line 698 print_func(cmd, target, source, env) 699 stat = 0 700 if execute: 701 if chdir: 702 os.chdir(chdir) 703 try: 704 stat = self.execute(target, source, env, executor=executor) 705 if isinstance(stat, SCons.Errors.BuildError): 706 s = exitstatfunc(stat.status) 707 if s: 708 stat.status = s 709 else: 710 stat = s 711 else: 712 stat = exitstatfunc(stat) 713 finally: 714 if save_cwd: 715 os.chdir(save_cwd) 716 if cmd and save_cwd: 717 print_func('os.chdir(%s)' % repr(save_cwd), target, source, env) 718 719 return stat 720 721 722def _string_from_cmd_list(cmd_list): 723 """Takes a list of command line arguments and returns a pretty 724 representation for printing.""" 725 cl = [] 726 for arg in map(str, cmd_list): 727 if ' ' in arg or '\t' in arg: 728 arg = '"' + arg + '"' 729 cl.append(arg) 730 return ' '.join(cl) 731 732default_ENV = None 733 734 735def get_default_ENV(env): 736 """ 737 A fiddlin' little function that has an 'import SCons.Environment' which 738 can't be moved to the top level without creating an import loop. Since 739 this import creates a local variable named 'SCons', it blocks access to 740 the global variable, so we move it here to prevent complaints about local 741 variables being used uninitialized. 742 """ 743 global default_ENV 744 try: 745 return env['ENV'] 746 except KeyError: 747 if not default_ENV: 748 import SCons.Environment 749 # This is a hideously expensive way to get a default shell 750 # environment. What it really should do is run the platform 751 # setup to get the default ENV. Fortunately, it's incredibly 752 # rare for an Environment not to have a shell environment, so 753 # we're not going to worry about it overmuch. 754 default_ENV = SCons.Environment.Environment()['ENV'] 755 return default_ENV 756 757 758def _subproc(scons_env, cmd, error='ignore', **kw): 759 """Wrapper for subprocess which pulls from construction env. 760 761 Use for calls to subprocess which need to interpolate values from 762 an SCons construction environment into the environment passed to 763 subprocess. Adds an an error-handling argument. Adds ability 764 to specify std{in,out,err} with "'devnull'" tag. 765 """ 766 # TODO: just uses subprocess.DEVNULL now, we can drop the "devnull" 767 # string now - it is a holdover from Py2, which didn't have DEVNULL. 768 for stream in 'stdin', 'stdout', 'stderr': 769 io = kw.get(stream) 770 if is_String(io) and io == 'devnull': 771 kw[stream] = DEVNULL 772 773 # Figure out what shell environment to use 774 ENV = kw.get('env', None) 775 if ENV is None: ENV = get_default_ENV(scons_env) 776 777 # Ensure that the ENV values are all strings: 778 new_env = {} 779 for key, value in ENV.items(): 780 if is_List(value): 781 # If the value is a list, then we assume it is a path list, 782 # because that's a pretty common list-like value to stick 783 # in an environment variable: 784 value = SCons.Util.flatten_sequence(value) 785 new_env[key] = os.pathsep.join(map(str, value)) 786 else: 787 # It's either a string or something else. If it's a string, 788 # we still want to call str() because it might be a *Unicode* 789 # string, which makes subprocess.Popen() gag. If it isn't a 790 # string or a list, then we just coerce it to a string, which 791 # is the proper way to handle Dir and File instances and will 792 # produce something reasonable for just about everything else: 793 new_env[key] = str(value) 794 kw['env'] = new_env 795 796 try: 797 pobj = subprocess.Popen(cmd, **kw) 798 except EnvironmentError as e: 799 if error == 'raise': raise 800 # return a dummy Popen instance that only returns error 801 class dummyPopen: 802 def __init__(self, e): self.exception = e 803 def communicate(self, input=None): return ('', '') 804 def wait(self): return -self.exception.errno 805 stdin = None 806 class f: 807 def read(self): return '' 808 def readline(self): return '' 809 def __iter__(self): return iter(()) 810 stdout = stderr = f() 811 pobj = dummyPopen(e) 812 finally: 813 # clean up open file handles stored in parent's kw 814 for k, v in kw.items(): 815 if inspect.ismethod(getattr(v, 'close', None)): 816 v.close() 817 818 return pobj 819 820 821class CommandAction(_ActionAction): 822 """Class for command-execution actions.""" 823 def __init__(self, cmd, **kw): 824 # Cmd can actually be a list or a single item; if it's a 825 # single item it should be the command string to execute; if a 826 # list then it should be the words of the command string to 827 # execute. Only a single command should be executed by this 828 # object; lists of commands should be handled by embedding 829 # these objects in a ListAction object (which the Action() 830 # factory above does). cmd will be passed to 831 # Environment.subst_list() for substituting environment 832 # variables. 833 if SCons.Debug.track_instances: logInstanceCreation(self, 'Action.CommandAction') 834 835 _ActionAction.__init__(self, **kw) 836 if is_List(cmd): 837 if [c for c in cmd if is_List(c)]: 838 raise TypeError("CommandAction should be given only " 839 "a single command") 840 self.cmd_list = cmd 841 842 def __str__(self): 843 if is_List(self.cmd_list): 844 return ' '.join(map(str, self.cmd_list)) 845 return str(self.cmd_list) 846 847 def process(self, target, source, env, executor=None): 848 if executor: 849 result = env.subst_list(self.cmd_list, 0, executor=executor) 850 else: 851 result = env.subst_list(self.cmd_list, 0, target, source) 852 silent = None 853 ignore = None 854 while True: 855 try: c = result[0][0][0] 856 except IndexError: c = None 857 if c == '@': silent = 1 858 elif c == '-': ignore = 1 859 else: break 860 result[0][0] = result[0][0][1:] 861 try: 862 if not result[0][0]: 863 result[0] = result[0][1:] 864 except IndexError: 865 pass 866 return result, ignore, silent 867 868 def strfunction(self, target, source, env, executor=None): 869 if self.cmdstr is None: 870 return None 871 if self.cmdstr is not _null: 872 from SCons.Subst import SUBST_RAW 873 if executor: 874 c = env.subst(self.cmdstr, SUBST_RAW, executor=executor) 875 else: 876 c = env.subst(self.cmdstr, SUBST_RAW, target, source) 877 if c: 878 return c 879 cmd_list, ignore, silent = self.process(target, source, env, executor) 880 if silent: 881 return '' 882 return _string_from_cmd_list(cmd_list[0]) 883 884 def execute(self, target, source, env, executor=None): 885 """Execute a command action. 886 887 This will handle lists of commands as well as individual commands, 888 because construction variable substitution may turn a single 889 "command" into a list. This means that this class can actually 890 handle lists of commands, even though that's not how we use it 891 externally. 892 """ 893 escape_list = SCons.Subst.escape_list 894 flatten_sequence = SCons.Util.flatten_sequence 895 896 try: 897 shell = env['SHELL'] 898 except KeyError: 899 raise SCons.Errors.UserError('Missing SHELL construction variable.') 900 901 try: 902 spawn = env['SPAWN'] 903 except KeyError: 904 raise SCons.Errors.UserError('Missing SPAWN construction variable.') 905 else: 906 if is_String(spawn): 907 spawn = env.subst(spawn, raw=1, conv=lambda x: x) 908 909 escape = env.get('ESCAPE', lambda x: x) 910 911 ENV = get_default_ENV(env) 912 913 # Ensure that the ENV values are all strings: 914 for key, value in ENV.items(): 915 if not is_String(value): 916 if is_List(value): 917 # If the value is a list, then we assume it is a 918 # path list, because that's a pretty common list-like 919 # value to stick in an environment variable: 920 value = flatten_sequence(value) 921 ENV[key] = os.pathsep.join(map(str, value)) 922 else: 923 # If it isn't a string or a list, then we just coerce 924 # it to a string, which is the proper way to handle 925 # Dir and File instances and will produce something 926 # reasonable for just about everything else: 927 ENV[key] = str(value) 928 929 if executor: 930 target = executor.get_all_targets() 931 source = executor.get_all_sources() 932 cmd_list, ignore, silent = self.process(target, list(map(rfile, source)), env, executor) 933 934 # Use len() to filter out any "command" that's zero-length. 935 for cmd_line in filter(len, cmd_list): 936 # Escape the command line for the interpreter we are using. 937 cmd_line = escape_list(cmd_line, escape) 938 result = spawn(shell, escape, cmd_line[0], cmd_line, ENV) 939 if not ignore and result: 940 msg = "Error %s" % result 941 return SCons.Errors.BuildError(errstr=msg, 942 status=result, 943 action=self, 944 command=cmd_line) 945 return 0 946 947 def get_presig(self, target, source, env, executor=None): 948 """Return the signature contents of this action's command line. 949 950 This strips $(-$) and everything in between the string, 951 since those parts don't affect signatures. 952 """ 953 from SCons.Subst import SUBST_SIG 954 cmd = self.cmd_list 955 if is_List(cmd): 956 cmd = ' '.join(map(str, cmd)) 957 else: 958 cmd = str(cmd) 959 if executor: 960 return env.subst_target_source(cmd, SUBST_SIG, executor=executor) 961 else: 962 return env.subst_target_source(cmd, SUBST_SIG, target, source) 963 964 def get_implicit_deps(self, target, source, env, executor=None): 965 """Return the implicit dependencies of this action's command line.""" 966 icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True) 967 if is_String(icd) and icd[:1] == '$': 968 icd = env.subst(icd) 969 970 if not icd or str(icd).lower() in ('0', 'none', 'false', 'no', 'off'): 971 return [] 972 973 try: 974 icd_int = int(icd) 975 except ValueError: 976 icd_int = None 977 978 if (icd_int and icd_int > 1) or str(icd).lower() == 'all': 979 # An integer value greater than 1 specifies the number of entries 980 # to scan. "all" means to scan all. 981 return self._get_implicit_deps_heavyweight(target, source, env, executor, icd_int) 982 else: 983 # Everything else (usually 1 or True) means that we want 984 # lightweight dependency scanning. 985 return self._get_implicit_deps_lightweight(target, source, env, executor) 986 987 def _get_implicit_deps_lightweight(self, target, source, env, executor): 988 """ 989 Lightweight dependency scanning involves only scanning the first entry 990 in an action string, even if it contains &&. 991 """ 992 from SCons.Subst import SUBST_SIG 993 if executor: 994 cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, executor=executor) 995 else: 996 cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, target, source) 997 res = [] 998 for cmd_line in cmd_list: 999 if cmd_line: 1000 d = str(cmd_line[0]) 1001 m = strip_quotes.match(d) 1002 if m: 1003 d = m.group(1) 1004 d = env.WhereIs(d) 1005 if d: 1006 res.append(env.fs.File(d)) 1007 return res 1008 1009 def _get_implicit_deps_heavyweight(self, target, source, env, executor, 1010 icd_int): 1011 """ 1012 Heavyweight dependency scanning involves scanning more than just the 1013 first entry in an action string. The exact behavior depends on the 1014 value of icd_int. Only files are taken as implicit dependencies; 1015 directories are ignored. 1016 1017 If icd_int is an integer value, it specifies the number of entries to 1018 scan for implicit dependencies. Action strings are also scanned after 1019 a &&. So for example, if icd_int=2 and the action string is 1020 "cd <some_dir> && $PYTHON $SCRIPT_PATH <another_path>", the implicit 1021 dependencies would be the path to the python binary and the path to the 1022 script. 1023 1024 If icd_int is None, all entries are scanned for implicit dependencies. 1025 """ 1026 1027 # Avoid circular and duplicate dependencies by not providing source, 1028 # target, or executor to subst_list. This causes references to 1029 # $SOURCES, $TARGETS, and all related variables to disappear. 1030 from SCons.Subst import SUBST_SIG 1031 cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, conv=lambda x: x) 1032 res = [] 1033 1034 for cmd_line in cmd_list: 1035 if cmd_line: 1036 entry_count = 0 1037 for entry in cmd_line: 1038 d = str(entry) 1039 if ((icd_int is None or entry_count < icd_int) and 1040 not d.startswith(('&', '-', '/') if os.name == 'nt' 1041 else ('&', '-'))): 1042 m = strip_quotes.match(d) 1043 if m: 1044 d = m.group(1) 1045 1046 if d: 1047 # Resolve the first entry in the command string using 1048 # PATH, which env.WhereIs() looks in. 1049 # For now, only match files, not directories. 1050 p = os.path.abspath(d) if os.path.isfile(d) else None 1051 if not p and entry_count == 0: 1052 p = env.WhereIs(d) 1053 1054 if p: 1055 res.append(env.fs.File(p)) 1056 1057 entry_count = entry_count + 1 1058 else: 1059 entry_count = 0 if d == '&&' else entry_count + 1 1060 1061 # Despite not providing source and target to env.subst() above, we 1062 # can still end up with sources in this list. For example, files in 1063 # LIBS will still resolve in env.subst(). This won't result in 1064 # circular dependencies, but it causes problems with cache signatures 1065 # changing between full and incremental builds. 1066 return [r for r in res if r not in target and r not in source] 1067 1068 1069class CommandGeneratorAction(ActionBase): 1070 """Class for command-generator actions.""" 1071 def __init__(self, generator, kw): 1072 if SCons.Debug.track_instances: logInstanceCreation(self, 'Action.CommandGeneratorAction') 1073 self.generator = generator 1074 self.gen_kw = kw 1075 self.varlist = kw.get('varlist', ()) 1076 self.targets = kw.get('targets', '$TARGETS') 1077 1078 def _generate(self, target, source, env, for_signature, executor=None): 1079 # ensure that target is a list, to make it easier to write 1080 # generator functions: 1081 if not is_List(target): 1082 target = [target] 1083 1084 if executor: 1085 target = executor.get_all_targets() 1086 source = executor.get_all_sources() 1087 ret = self.generator(target=target, 1088 source=source, 1089 env=env, 1090 for_signature=for_signature) 1091 gen_cmd = Action(ret, **self.gen_kw) 1092 if not gen_cmd: 1093 raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret)) 1094 return gen_cmd 1095 1096 def __str__(self): 1097 try: 1098 env = self.presub_env 1099 except AttributeError: 1100 env = None 1101 if env is None: 1102 env = SCons.Defaults.DefaultEnvironment() 1103 act = self._generate([], [], env, 1) 1104 return str(act) 1105 1106 def batch_key(self, env, target, source): 1107 return self._generate(target, source, env, 1).batch_key(env, target, source) 1108 1109 def genstring(self, target, source, env, executor=None): 1110 return self._generate(target, source, env, 1, executor).genstring(target, source, env) 1111 1112 def __call__(self, target, source, env, exitstatfunc=_null, presub=_null, 1113 show=_null, execute=_null, chdir=_null, executor=None): 1114 act = self._generate(target, source, env, 0, executor) 1115 if act is None: 1116 raise SCons.Errors.UserError( 1117 "While building `%s': " 1118 "Cannot deduce file extension from source files: %s" 1119 % (repr(list(map(str, target))), repr(list(map(str, source)))) 1120 ) 1121 return act( 1122 target, source, env, exitstatfunc, presub, show, execute, chdir, executor 1123 ) 1124 1125 def get_presig(self, target, source, env, executor=None): 1126 """Return the signature contents of this action's command line. 1127 1128 This strips $(-$) and everything in between the string, 1129 since those parts don't affect signatures. 1130 """ 1131 return self._generate(target, source, env, 1, executor).get_presig(target, source, env) 1132 1133 def get_implicit_deps(self, target, source, env, executor=None): 1134 return self._generate(target, source, env, 1, executor).get_implicit_deps(target, source, env) 1135 1136 def get_varlist(self, target, source, env, executor=None): 1137 return self._generate(target, source, env, 1, executor).get_varlist(target, source, env, executor) 1138 1139 def get_targets(self, env, executor): 1140 return self._generate(None, None, env, 1, executor).get_targets(env, executor) 1141 1142 1143class LazyAction(CommandGeneratorAction, CommandAction): 1144 """ 1145 A LazyAction is a kind of hybrid generator and command action for 1146 strings of the form "$VAR". These strings normally expand to other 1147 strings (think "$CCCOM" to "$CC -c -o $TARGET $SOURCE"), but we also 1148 want to be able to replace them with functions in the construction 1149 environment. Consequently, we want lazy evaluation and creation of 1150 an Action in the case of the function, but that's overkill in the more 1151 normal case of expansion to other strings. 1152 1153 So we do this with a subclass that's both a generator *and* 1154 a command action. The overridden methods all do a quick check 1155 of the construction variable, and if it's a string we just call 1156 the corresponding CommandAction method to do the heavy lifting. 1157 If not, then we call the same-named CommandGeneratorAction method. 1158 The CommandGeneratorAction methods work by using the overridden 1159 _generate() method, that is, our own way of handling "generation" of 1160 an action based on what's in the construction variable. 1161 """ 1162 1163 def __init__(self, var, kw): 1164 if SCons.Debug.track_instances: logInstanceCreation(self, 'Action.LazyAction') 1165 CommandAction.__init__(self, '${'+var+'}', **kw) 1166 self.var = SCons.Util.to_String(var) 1167 self.gen_kw = kw 1168 1169 def get_parent_class(self, env): 1170 c = env.get(self.var) 1171 if is_String(c) and '\n' not in c: 1172 return CommandAction 1173 return CommandGeneratorAction 1174 1175 def _generate_cache(self, env): 1176 if env: 1177 c = env.get(self.var, '') 1178 else: 1179 c = '' 1180 gen_cmd = Action(c, **self.gen_kw) 1181 if not gen_cmd: 1182 raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c))) 1183 return gen_cmd 1184 1185 def _generate(self, target, source, env, for_signature, executor=None): 1186 return self._generate_cache(env) 1187 1188 def __call__(self, target, source, env, *args, **kw): 1189 c = self.get_parent_class(env) 1190 return c.__call__(self, target, source, env, *args, **kw) 1191 1192 def get_presig(self, target, source, env): 1193 c = self.get_parent_class(env) 1194 return c.get_presig(self, target, source, env) 1195 1196 def get_varlist(self, target, source, env, executor=None): 1197 c = self.get_parent_class(env) 1198 return c.get_varlist(self, target, source, env, executor) 1199 1200 1201class FunctionAction(_ActionAction): 1202 """Class for Python function actions.""" 1203 1204 def __init__(self, execfunction, kw): 1205 if SCons.Debug.track_instances: logInstanceCreation(self, 'Action.FunctionAction') 1206 1207 self.execfunction = execfunction 1208 try: 1209 self.funccontents = _callable_contents(execfunction) 1210 except AttributeError: 1211 try: 1212 # See if execfunction will do the heavy lifting for us. 1213 self.gc = execfunction.get_contents 1214 except AttributeError: 1215 # This is weird, just do the best we can. 1216 self.funccontents = _object_contents(execfunction) 1217 1218 _ActionAction.__init__(self, **kw) 1219 1220 def function_name(self): 1221 try: 1222 return self.execfunction.__name__ 1223 except AttributeError: 1224 try: 1225 return self.execfunction.__class__.__name__ 1226 except AttributeError: 1227 return "unknown_python_function" 1228 1229 def strfunction(self, target, source, env, executor=None): 1230 if self.cmdstr is None: 1231 return None 1232 if self.cmdstr is not _null: 1233 from SCons.Subst import SUBST_RAW 1234 if executor: 1235 c = env.subst(self.cmdstr, SUBST_RAW, executor=executor) 1236 else: 1237 c = env.subst(self.cmdstr, SUBST_RAW, target, source) 1238 if c: 1239 return c 1240 1241 def array(a): 1242 def quote(s): 1243 try: 1244 str_for_display = s.str_for_display 1245 except AttributeError: 1246 s = repr(s) 1247 else: 1248 s = str_for_display() 1249 return s 1250 return '[' + ", ".join(map(quote, a)) + ']' 1251 try: 1252 strfunc = self.execfunction.strfunction 1253 except AttributeError: 1254 pass 1255 else: 1256 if strfunc is None: 1257 return None 1258 if callable(strfunc): 1259 return strfunc(target, source, env) 1260 name = self.function_name() 1261 tstr = array(target) 1262 sstr = array(source) 1263 return "%s(%s, %s)" % (name, tstr, sstr) 1264 1265 def __str__(self): 1266 name = self.function_name() 1267 if name == 'ActionCaller': 1268 return str(self.execfunction) 1269 return "%s(target, source, env)" % name 1270 1271 def execute(self, target, source, env, executor=None): 1272 exc_info = (None,None,None) 1273 try: 1274 if executor: 1275 target = executor.get_all_targets() 1276 source = executor.get_all_sources() 1277 rsources = list(map(rfile, source)) 1278 try: 1279 result = self.execfunction(target=target, source=rsources, env=env) 1280 except KeyboardInterrupt as e: 1281 raise 1282 except SystemExit as e: 1283 raise 1284 except Exception as e: 1285 result = e 1286 exc_info = sys.exc_info() 1287 1288 if result: 1289 result = SCons.Errors.convert_to_BuildError(result, exc_info) 1290 result.node=target 1291 result.action=self 1292 try: 1293 result.command=self.strfunction(target, source, env, executor) 1294 except TypeError: 1295 result.command=self.strfunction(target, source, env) 1296 1297 # FIXME: This maintains backward compatibility with respect to 1298 # which type of exceptions were returned by raising an 1299 # exception and which ones were returned by value. It would 1300 # probably be best to always return them by value here, but 1301 # some codes do not check the return value of Actions and I do 1302 # not have the time to modify them at this point. 1303 if (exc_info[1] and 1304 not isinstance(exc_info[1],EnvironmentError)): 1305 raise result 1306 1307 return result 1308 finally: 1309 # Break the cycle between the traceback object and this 1310 # function stack frame. See the sys.exc_info() doc info for 1311 # more information about this issue. 1312 del exc_info 1313 1314 def get_presig(self, target, source, env): 1315 """Return the signature contents of this callable action.""" 1316 try: 1317 return self.gc(target, source, env) 1318 except AttributeError: 1319 return self.funccontents 1320 1321 def get_implicit_deps(self, target, source, env): 1322 return [] 1323 1324class ListAction(ActionBase): 1325 """Class for lists of other actions.""" 1326 def __init__(self, actionlist): 1327 if SCons.Debug.track_instances: logInstanceCreation(self, 'Action.ListAction') 1328 def list_of_actions(x): 1329 if isinstance(x, ActionBase): 1330 return x 1331 return Action(x) 1332 self.list = list(map(list_of_actions, actionlist)) 1333 # our children will have had any varlist 1334 # applied; we don't need to do it again 1335 self.varlist = () 1336 self.targets = '$TARGETS' 1337 1338 def genstring(self, target, source, env): 1339 return '\n'.join([a.genstring(target, source, env) for a in self.list]) 1340 1341 def __str__(self): 1342 return '\n'.join(map(str, self.list)) 1343 1344 def presub_lines(self, env): 1345 return SCons.Util.flatten_sequence( 1346 [a.presub_lines(env) for a in self.list]) 1347 1348 def get_presig(self, target, source, env): 1349 """Return the signature contents of this action list. 1350 1351 Simple concatenation of the signatures of the elements. 1352 """ 1353 return b"".join([bytes(x.get_contents(target, source, env)) for x in self.list]) 1354 1355 def __call__(self, target, source, env, exitstatfunc=_null, presub=_null, 1356 show=_null, execute=_null, chdir=_null, executor=None): 1357 if executor: 1358 target = executor.get_all_targets() 1359 source = executor.get_all_sources() 1360 for act in self.list: 1361 stat = act(target, source, env, exitstatfunc, presub, 1362 show, execute, chdir, executor) 1363 if stat: 1364 return stat 1365 return 0 1366 1367 def get_implicit_deps(self, target, source, env): 1368 result = [] 1369 for act in self.list: 1370 result.extend(act.get_implicit_deps(target, source, env)) 1371 return result 1372 1373 def get_varlist(self, target, source, env, executor=None): 1374 result = OrderedDict() 1375 for act in self.list: 1376 for var in act.get_varlist(target, source, env, executor): 1377 result[var] = True 1378 return list(result.keys()) 1379 1380 1381class ActionCaller: 1382 """A class for delaying calling an Action function with specific 1383 (positional and keyword) arguments until the Action is actually 1384 executed. 1385 1386 This class looks to the rest of the world like a normal Action object, 1387 but what it's really doing is hanging on to the arguments until we 1388 have a target, source and env to use for the expansion. 1389 """ 1390 def __init__(self, parent, args, kw): 1391 self.parent = parent 1392 self.args = args 1393 self.kw = kw 1394 1395 def get_contents(self, target, source, env): 1396 actfunc = self.parent.actfunc 1397 try: 1398 # "self.actfunc" is a function. 1399 contents = actfunc.__code__.co_code 1400 except AttributeError: 1401 # "self.actfunc" is a callable object. 1402 try: 1403 contents = actfunc.__call__.__func__.__code__.co_code 1404 except AttributeError: 1405 # No __call__() method, so it might be a builtin 1406 # or something like that. Do the best we can. 1407 contents = repr(actfunc) 1408 1409 return contents 1410 1411 def subst(self, s, target, source, env): 1412 # If s is a list, recursively apply subst() 1413 # to every element in the list 1414 if is_List(s): 1415 result = [] 1416 for elem in s: 1417 result.append(self.subst(elem, target, source, env)) 1418 return self.parent.convert(result) 1419 1420 # Special-case hack: Let a custom function wrapped in an 1421 # ActionCaller get at the environment through which the action 1422 # was called by using this hard-coded value as a special return. 1423 if s == '$__env__': 1424 return env 1425 elif is_String(s): 1426 return env.subst(s, 1, target, source) 1427 return self.parent.convert(s) 1428 1429 def subst_args(self, target, source, env): 1430 return [self.subst(x, target, source, env) for x in self.args] 1431 1432 def subst_kw(self, target, source, env): 1433 kw = {} 1434 for key in list(self.kw.keys()): 1435 kw[key] = self.subst(self.kw[key], target, source, env) 1436 return kw 1437 1438 def __call__(self, target, source, env, executor=None): 1439 args = self.subst_args(target, source, env) 1440 kw = self.subst_kw(target, source, env) 1441 return self.parent.actfunc(*args, **kw) 1442 1443 def strfunction(self, target, source, env): 1444 args = self.subst_args(target, source, env) 1445 kw = self.subst_kw(target, source, env) 1446 return self.parent.strfunc(*args, **kw) 1447 1448 def __str__(self): 1449 return self.parent.strfunc(*self.args, **self.kw) 1450 1451 1452class ActionFactory: 1453 """A factory class that will wrap up an arbitrary function 1454 as an SCons-executable Action object. 1455 1456 The real heavy lifting here is done by the ActionCaller class. 1457 We just collect the (positional and keyword) arguments that we're 1458 called with and give them to the ActionCaller object we create, 1459 so it can hang onto them until it needs them. 1460 """ 1461 def __init__(self, actfunc, strfunc, convert=lambda x: x): 1462 self.actfunc = actfunc 1463 self.strfunc = strfunc 1464 self.convert = convert 1465 1466 def __call__(self, *args, **kw): 1467 ac = ActionCaller(self, args, kw) 1468 action = Action(ac, strfunction=ac.strfunction) 1469 return action 1470 1471# Local Variables: 1472# tab-width:4 1473# indent-tabs-mode:nil 1474# End: 1475# vim: set expandtab tabstop=4 shiftwidth=4: 1476