OLD | NEW |
(Empty) | |
| 1 """SCons.Action |
| 2 |
| 3 This encapsulates information about executing any sort of action that |
| 4 can build one or more target Nodes (typically files) from one or more |
| 5 source Nodes (also typically files) given a specific Environment. |
| 6 |
| 7 The base class here is ActionBase. The base class supplies just a few |
| 8 OO utility methods and some generic methods for displaying information |
| 9 about an Action in response to the various commands that control printing. |
| 10 |
| 11 A second-level base class is _ActionAction. This extends ActionBase |
| 12 by providing the methods that can be used to show and perform an |
| 13 action. True Action objects will subclass _ActionAction; Action |
| 14 factory class objects will subclass ActionBase. |
| 15 |
| 16 The heavy lifting is handled by subclasses for the different types of |
| 17 actions we might execute: |
| 18 |
| 19 CommandAction |
| 20 CommandGeneratorAction |
| 21 FunctionAction |
| 22 ListAction |
| 23 |
| 24 The subclasses supply the following public interface methods used by |
| 25 other modules: |
| 26 |
| 27 __call__() |
| 28 THE public interface, "calling" an Action object executes the |
| 29 command or Python function. This also takes care of printing |
| 30 a pre-substitution command for debugging purposes. |
| 31 |
| 32 get_contents() |
| 33 Fetches the "contents" of an Action for signature calculation |
| 34 plus the varlist. This is what gets MD5 checksummed to decide |
| 35 if a target needs to be rebuilt because its action changed. |
| 36 |
| 37 genstring() |
| 38 Returns a string representation of the Action *without* |
| 39 command substitution, but allows a CommandGeneratorAction to |
| 40 generate the right action based on the specified target, |
| 41 source and env. This is used by the Signature subsystem |
| 42 (through the Executor) to obtain an (imprecise) representation |
| 43 of the Action operation for informative purposes. |
| 44 |
| 45 |
| 46 Subclasses also supply the following methods for internal use within |
| 47 this module: |
| 48 |
| 49 __str__() |
| 50 Returns a string approximation of the Action; no variable |
| 51 substitution is performed. |
| 52 |
| 53 execute() |
| 54 The internal method that really, truly, actually handles the |
| 55 execution of a command or Python function. This is used so |
| 56 that the __call__() methods can take care of displaying any |
| 57 pre-substitution representations, and *then* execute an action |
| 58 without worrying about the specific Actions involved. |
| 59 |
| 60 get_presig() |
| 61 Fetches the "contents" of a subclass for signature calculation. |
| 62 The varlist is added to this to produce the Action's contents. |
| 63 |
| 64 strfunction() |
| 65 Returns a substituted string representation of the Action. |
| 66 This is used by the _ActionAction.show() command to display the |
| 67 command/function that will be executed to generate the target(s). |
| 68 |
| 69 There is a related independent ActionCaller class that looks like a |
| 70 regular Action, and which serves as a wrapper for arbitrary functions |
| 71 that we want to let the user specify the arguments to now, but actually |
| 72 execute later (when an out-of-date check determines that it's needed to |
| 73 be executed, for example). Objects of this class are returned by an |
| 74 ActionFactory class that provides a __call__() method as a convenient |
| 75 way for wrapping up the functions. |
| 76 |
| 77 """ |
| 78 |
| 79 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 The S
Cons Foundation |
| 80 # |
| 81 # Permission is hereby granted, free of charge, to any person obtaining |
| 82 # a copy of this software and associated documentation files (the |
| 83 # "Software"), to deal in the Software without restriction, including |
| 84 # without limitation the rights to use, copy, modify, merge, publish, |
| 85 # distribute, sublicense, and/or sell copies of the Software, and to |
| 86 # permit persons to whom the Software is furnished to do so, subject to |
| 87 # the following conditions: |
| 88 # |
| 89 # The above copyright notice and this permission notice shall be included |
| 90 # in all copies or substantial portions of the Software. |
| 91 # |
| 92 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY |
| 93 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE |
| 94 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| 95 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
| 96 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
| 97 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
| 98 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 99 |
| 100 __revision__ = "src/engine/SCons/Action.py 5134 2010/08/16 23:02:40 bdeegan" |
| 101 |
| 102 import SCons.compat |
| 103 |
| 104 import dis |
| 105 import os |
| 106 # compat layer imports "cPickle" for us if it's available. |
| 107 import pickle |
| 108 import re |
| 109 import sys |
| 110 import subprocess |
| 111 |
| 112 from SCons.Debug import logInstanceCreation |
| 113 import SCons.Errors |
| 114 import SCons.Executor |
| 115 import SCons.Util |
| 116 import SCons.Subst |
| 117 |
| 118 # we use these a lot, so try to optimize them |
| 119 is_String = SCons.Util.is_String |
| 120 is_List = SCons.Util.is_List |
| 121 |
| 122 class _null(object): |
| 123 pass |
| 124 |
| 125 print_actions = 1 |
| 126 execute_actions = 1 |
| 127 print_actions_presub = 0 |
| 128 |
| 129 def rfile(n): |
| 130 try: |
| 131 return n.rfile() |
| 132 except AttributeError: |
| 133 return n |
| 134 |
| 135 def default_exitstatfunc(s): |
| 136 return s |
| 137 |
| 138 try: |
| 139 SET_LINENO = dis.SET_LINENO |
| 140 HAVE_ARGUMENT = dis.HAVE_ARGUMENT |
| 141 except AttributeError: |
| 142 remove_set_lineno_codes = lambda x: x |
| 143 else: |
| 144 def remove_set_lineno_codes(code): |
| 145 result = [] |
| 146 n = len(code) |
| 147 i = 0 |
| 148 while i < n: |
| 149 c = code[i] |
| 150 op = ord(c) |
| 151 if op >= HAVE_ARGUMENT: |
| 152 if op != SET_LINENO: |
| 153 result.append(code[i:i+3]) |
| 154 i = i+3 |
| 155 else: |
| 156 result.append(c) |
| 157 i = i+1 |
| 158 return ''.join(result) |
| 159 |
| 160 strip_quotes = re.compile('^[\'"](.*)[\'"]$') |
| 161 |
| 162 |
| 163 def _callable_contents(obj): |
| 164 """Return the signature contents of a callable Python object. |
| 165 """ |
| 166 try: |
| 167 # Test if obj is a method. |
| 168 return _function_contents(obj.im_func) |
| 169 |
| 170 except AttributeError: |
| 171 try: |
| 172 # Test if obj is a callable object. |
| 173 return _function_contents(obj.__call__.im_func) |
| 174 |
| 175 except AttributeError: |
| 176 try: |
| 177 # Test if obj is a code object. |
| 178 return _code_contents(obj) |
| 179 |
| 180 except AttributeError: |
| 181 # Test if obj is a function object. |
| 182 return _function_contents(obj) |
| 183 |
| 184 |
| 185 def _object_contents(obj): |
| 186 """Return the signature contents of any Python object. |
| 187 |
| 188 We have to handle the case where object contains a code object |
| 189 since it can be pickled directly. |
| 190 """ |
| 191 try: |
| 192 # Test if obj is a method. |
| 193 return _function_contents(obj.im_func) |
| 194 |
| 195 except AttributeError: |
| 196 try: |
| 197 # Test if obj is a callable object. |
| 198 return _function_contents(obj.__call__.im_func) |
| 199 |
| 200 except AttributeError: |
| 201 try: |
| 202 # Test if obj is a code object. |
| 203 return _code_contents(obj) |
| 204 |
| 205 except AttributeError: |
| 206 try: |
| 207 # Test if obj is a function object. |
| 208 return _function_contents(obj) |
| 209 |
| 210 except AttributeError: |
| 211 # Should be a pickable Python object. |
| 212 try: |
| 213 return pickle.dumps(obj) |
| 214 except (pickle.PicklingError, TypeError): |
| 215 # This is weird, but it seems that nested classes |
| 216 # are unpickable. The Python docs say it should |
| 217 # always be a PicklingError, but some Python |
| 218 # versions seem to return TypeError. Just do |
| 219 # the best we can. |
| 220 return str(obj) |
| 221 |
| 222 |
| 223 def _code_contents(code): |
| 224 """Return the signature contents of a code object. |
| 225 |
| 226 By providing direct access to the code object of the |
| 227 function, Python makes this extremely easy. Hooray! |
| 228 |
| 229 Unfortunately, older versions of Python include line |
| 230 number indications in the compiled byte code. Boo! |
| 231 So we remove the line number byte codes to prevent |
| 232 recompilations from moving a Python function. |
| 233 """ |
| 234 |
| 235 contents = [] |
| 236 |
| 237 # The code contents depends on the number of local variables |
| 238 # but not their actual names. |
| 239 contents.append("%s,%s" % (code.co_argcount, len(code.co_varnames))) |
| 240 try: |
| 241 contents.append(",%s,%s" % (len(code.co_cellvars), len(code.co_freevars)
)) |
| 242 except AttributeError: |
| 243 # Older versions of Python do not support closures. |
| 244 contents.append(",0,0") |
| 245 |
| 246 # The code contents depends on any constants accessed by the |
| 247 # function. Note that we have to call _object_contents on each |
| 248 # constants because the code object of nested functions can |
| 249 # show-up among the constants. |
| 250 # |
| 251 # Note that we also always ignore the first entry of co_consts |
| 252 # which contains the function doc string. We assume that the |
| 253 # function does not access its doc string. |
| 254 contents.append(',(' + ','.join(map(_object_contents,code.co_consts[1:])) +
')') |
| 255 |
| 256 # The code contents depends on the variable names used to |
| 257 # accessed global variable, as changing the variable name changes |
| 258 # the variable actually accessed and therefore changes the |
| 259 # function result. |
| 260 contents.append(',(' + ','.join(map(_object_contents,code.co_names)) + ')') |
| 261 |
| 262 |
| 263 # The code contents depends on its actual code!!! |
| 264 contents.append(',(' + str(remove_set_lineno_codes(code.co_code)) + ')') |
| 265 |
| 266 return ''.join(contents) |
| 267 |
| 268 |
| 269 def _function_contents(func): |
| 270 """Return the signature contents of a function.""" |
| 271 |
| 272 contents = [_code_contents(func.func_code)] |
| 273 |
| 274 # The function contents depends on the value of defaults arguments |
| 275 if func.func_defaults: |
| 276 contents.append(',(' + ','.join(map(_object_contents,func.func_defaults)
) + ')') |
| 277 else: |
| 278 contents.append(',()') |
| 279 |
| 280 # The function contents depends on the closure captured cell values. |
| 281 try: |
| 282 closure = func.func_closure or [] |
| 283 except AttributeError: |
| 284 # Older versions of Python do not support closures. |
| 285 closure = [] |
| 286 |
| 287 #xxx = [_object_contents(x.cell_contents) for x in closure] |
| 288 try: |
| 289 xxx = [_object_contents(x.cell_contents) for x in closure] |
| 290 except AttributeError: |
| 291 xxx = [] |
| 292 contents.append(',(' + ','.join(xxx) + ')') |
| 293 |
| 294 return ''.join(contents) |
| 295 |
| 296 |
| 297 def _actionAppend(act1, act2): |
| 298 # This function knows how to slap two actions together. |
| 299 # Mainly, it handles ListActions by concatenating into |
| 300 # a single ListAction. |
| 301 a1 = Action(act1) |
| 302 a2 = Action(act2) |
| 303 if a1 is None or a2 is None: |
| 304 raise TypeError("Cannot append %s to %s" % (type(act1), type(act2))) |
| 305 if isinstance(a1, ListAction): |
| 306 if isinstance(a2, ListAction): |
| 307 return ListAction(a1.list + a2.list) |
| 308 else: |
| 309 return ListAction(a1.list + [ a2 ]) |
| 310 else: |
| 311 if isinstance(a2, ListAction): |
| 312 return ListAction([ a1 ] + a2.list) |
| 313 else: |
| 314 return ListAction([ a1, a2 ]) |
| 315 |
| 316 def _do_create_keywords(args, kw): |
| 317 """This converts any arguments after the action argument into |
| 318 their equivalent keywords and adds them to the kw argument. |
| 319 """ |
| 320 v = kw.get('varlist', ()) |
| 321 # prevent varlist="FOO" from being interpreted as ['F', 'O', 'O'] |
| 322 if is_String(v): v = (v,) |
| 323 kw['varlist'] = tuple(v) |
| 324 if args: |
| 325 # turn positional args into equivalent keywords |
| 326 cmdstrfunc = args[0] |
| 327 if cmdstrfunc is None or is_String(cmdstrfunc): |
| 328 kw['cmdstr'] = cmdstrfunc |
| 329 elif callable(cmdstrfunc): |
| 330 kw['strfunction'] = cmdstrfunc |
| 331 else: |
| 332 raise SCons.Errors.UserError( |
| 333 'Invalid command display variable type. ' |
| 334 'You must either pass a string or a callback which ' |
| 335 'accepts (target, source, env) as parameters.') |
| 336 if len(args) > 1: |
| 337 kw['varlist'] = args[1:] + kw['varlist'] |
| 338 if kw.get('strfunction', _null) is not _null \ |
| 339 and kw.get('cmdstr', _null) is not _null: |
| 340 raise SCons.Errors.UserError( |
| 341 'Cannot have both strfunction and cmdstr args to Action()') |
| 342 |
| 343 def _do_create_action(act, kw): |
| 344 """This is the actual "implementation" for the |
| 345 Action factory method, below. This handles the |
| 346 fact that passing lists to Action() itself has |
| 347 different semantics than passing lists as elements |
| 348 of lists. |
| 349 |
| 350 The former will create a ListAction, the latter |
| 351 will create a CommandAction by converting the inner |
| 352 list elements to strings.""" |
| 353 |
| 354 if isinstance(act, ActionBase): |
| 355 return act |
| 356 |
| 357 if is_List(act): |
| 358 return CommandAction(act, **kw) |
| 359 |
| 360 if callable(act): |
| 361 try: |
| 362 gen = kw['generator'] |
| 363 del kw['generator'] |
| 364 except KeyError: |
| 365 gen = 0 |
| 366 if gen: |
| 367 action_type = CommandGeneratorAction |
| 368 else: |
| 369 action_type = FunctionAction |
| 370 return action_type(act, kw) |
| 371 |
| 372 if is_String(act): |
| 373 var=SCons.Util.get_environment_var(act) |
| 374 if var: |
| 375 # This looks like a string that is purely an Environment |
| 376 # variable reference, like "$FOO" or "${FOO}". We do |
| 377 # something special here...we lazily evaluate the contents |
| 378 # of that Environment variable, so a user could put something |
| 379 # like a function or a CommandGenerator in that variable |
| 380 # instead of a string. |
| 381 return LazyAction(var, kw) |
| 382 commands = str(act).split('\n') |
| 383 if len(commands) == 1: |
| 384 return CommandAction(commands[0], **kw) |
| 385 # The list of string commands may include a LazyAction, so we |
| 386 # reprocess them via _do_create_list_action. |
| 387 return _do_create_list_action(commands, kw) |
| 388 return None |
| 389 |
| 390 def _do_create_list_action(act, kw): |
| 391 """A factory for list actions. Convert the input list into Actions |
| 392 and then wrap them in a ListAction.""" |
| 393 acts = [] |
| 394 for a in act: |
| 395 aa = _do_create_action(a, kw) |
| 396 if aa is not None: acts.append(aa) |
| 397 if not acts: |
| 398 return ListAction([]) |
| 399 elif len(acts) == 1: |
| 400 return acts[0] |
| 401 else: |
| 402 return ListAction(acts) |
| 403 |
| 404 def Action(act, *args, **kw): |
| 405 """A factory for action objects.""" |
| 406 # Really simple: the _do_create_* routines do the heavy lifting. |
| 407 _do_create_keywords(args, kw) |
| 408 if is_List(act): |
| 409 return _do_create_list_action(act, kw) |
| 410 return _do_create_action(act, kw) |
| 411 |
| 412 class ActionBase(object): |
| 413 """Base class for all types of action objects that can be held by |
| 414 other objects (Builders, Executors, etc.) This provides the |
| 415 common methods for manipulating and combining those actions.""" |
| 416 |
| 417 def __cmp__(self, other): |
| 418 return cmp(self.__dict__, other) |
| 419 |
| 420 def no_batch_key(self, env, target, source): |
| 421 return None |
| 422 |
| 423 batch_key = no_batch_key |
| 424 |
| 425 def genstring(self, target, source, env): |
| 426 return str(self) |
| 427 |
| 428 def get_contents(self, target, source, env): |
| 429 result = [ self.get_presig(target, source, env) ] |
| 430 # This should never happen, as the Action() factory should wrap |
| 431 # the varlist, but just in case an action is created directly, |
| 432 # we duplicate this check here. |
| 433 vl = self.get_varlist(target, source, env) |
| 434 if is_String(vl): vl = (vl,) |
| 435 for v in vl: |
| 436 result.append(env.subst('${'+v+'}')) |
| 437 return ''.join(result) |
| 438 |
| 439 def __add__(self, other): |
| 440 return _actionAppend(self, other) |
| 441 |
| 442 def __radd__(self, other): |
| 443 return _actionAppend(other, self) |
| 444 |
| 445 def presub_lines(self, env): |
| 446 # CommandGeneratorAction needs a real environment |
| 447 # in order to return the proper string here, since |
| 448 # it may call LazyAction, which looks up a key |
| 449 # in that env. So we temporarily remember the env here, |
| 450 # and CommandGeneratorAction will use this env |
| 451 # when it calls its _generate method. |
| 452 self.presub_env = env |
| 453 lines = str(self).split('\n') |
| 454 self.presub_env = None # don't need this any more |
| 455 return lines |
| 456 |
| 457 def get_varlist(self, target, source, env, executor=None): |
| 458 return self.varlist |
| 459 |
| 460 def get_targets(self, env, executor): |
| 461 """ |
| 462 Returns the type of targets ($TARGETS, $CHANGED_TARGETS) used |
| 463 by this action. |
| 464 """ |
| 465 return self.targets |
| 466 |
| 467 class _ActionAction(ActionBase): |
| 468 """Base class for actions that create output objects.""" |
| 469 def __init__(self, cmdstr=_null, strfunction=_null, varlist=(), |
| 470 presub=_null, chdir=None, exitstatfunc=None, |
| 471 batch_key=None, targets='$TARGETS', |
| 472 **kw): |
| 473 self.cmdstr = cmdstr |
| 474 if strfunction is not _null: |
| 475 if strfunction is None: |
| 476 self.cmdstr = None |
| 477 else: |
| 478 self.strfunction = strfunction |
| 479 self.varlist = varlist |
| 480 self.presub = presub |
| 481 self.chdir = chdir |
| 482 if not exitstatfunc: |
| 483 exitstatfunc = default_exitstatfunc |
| 484 self.exitstatfunc = exitstatfunc |
| 485 |
| 486 self.targets = targets |
| 487 |
| 488 if batch_key: |
| 489 if not callable(batch_key): |
| 490 # They have set batch_key, but not to their own |
| 491 # callable. The default behavior here will batch |
| 492 # *all* targets+sources using this action, separated |
| 493 # for each construction environment. |
| 494 def default_batch_key(self, env, target, source): |
| 495 return (id(self), id(env)) |
| 496 batch_key = default_batch_key |
| 497 SCons.Util.AddMethod(self, batch_key, 'batch_key') |
| 498 |
| 499 def print_cmd_line(self, s, target, source, env): |
| 500 sys.stdout.write(s + u"\n") |
| 501 |
| 502 def __call__(self, target, source, env, |
| 503 exitstatfunc=_null, |
| 504 presub=_null, |
| 505 show=_null, |
| 506 execute=_null, |
| 507 chdir=_null, |
| 508 executor=None): |
| 509 if not is_List(target): |
| 510 target = [target] |
| 511 if not is_List(source): |
| 512 source = [source] |
| 513 |
| 514 if presub is _null: |
| 515 presub = self.presub |
| 516 if presub is _null: |
| 517 presub = print_actions_presub |
| 518 if exitstatfunc is _null: exitstatfunc = self.exitstatfunc |
| 519 if show is _null: show = print_actions |
| 520 if execute is _null: execute = execute_actions |
| 521 if chdir is _null: chdir = self.chdir |
| 522 save_cwd = None |
| 523 if chdir: |
| 524 save_cwd = os.getcwd() |
| 525 try: |
| 526 chdir = str(chdir.abspath) |
| 527 except AttributeError: |
| 528 if not is_String(chdir): |
| 529 if executor: |
| 530 chdir = str(executor.batches[0].targets[0].dir) |
| 531 else: |
| 532 chdir = str(target[0].dir) |
| 533 if presub: |
| 534 if executor: |
| 535 target = executor.get_all_targets() |
| 536 source = executor.get_all_sources() |
| 537 t = ' and '.join(map(str, target)) |
| 538 l = '\n '.join(self.presub_lines(env)) |
| 539 out = u"Building %s with action:\n %s\n" % (t, l) |
| 540 sys.stdout.write(out) |
| 541 cmd = None |
| 542 if show and self.strfunction: |
| 543 if executor: |
| 544 target = executor.get_all_targets() |
| 545 source = executor.get_all_sources() |
| 546 try: |
| 547 cmd = self.strfunction(target, source, env, executor) |
| 548 except TypeError: |
| 549 cmd = self.strfunction(target, source, env) |
| 550 if cmd: |
| 551 if chdir: |
| 552 cmd = ('os.chdir(%s)\n' % repr(chdir)) + cmd |
| 553 try: |
| 554 get = env.get |
| 555 except AttributeError: |
| 556 print_func = self.print_cmd_line |
| 557 else: |
| 558 print_func = get('PRINT_CMD_LINE_FUNC') |
| 559 if not print_func: |
| 560 print_func = self.print_cmd_line |
| 561 print_func(cmd, target, source, env) |
| 562 stat = 0 |
| 563 if execute: |
| 564 if chdir: |
| 565 os.chdir(chdir) |
| 566 try: |
| 567 stat = self.execute(target, source, env, executor=executor) |
| 568 if isinstance(stat, SCons.Errors.BuildError): |
| 569 s = exitstatfunc(stat.status) |
| 570 if s: |
| 571 stat.status = s |
| 572 else: |
| 573 stat = s |
| 574 else: |
| 575 stat = exitstatfunc(stat) |
| 576 finally: |
| 577 if save_cwd: |
| 578 os.chdir(save_cwd) |
| 579 if cmd and save_cwd: |
| 580 print_func('os.chdir(%s)' % repr(save_cwd), target, source, env) |
| 581 |
| 582 return stat |
| 583 |
| 584 |
| 585 def _string_from_cmd_list(cmd_list): |
| 586 """Takes a list of command line arguments and returns a pretty |
| 587 representation for printing.""" |
| 588 cl = [] |
| 589 for arg in map(str, cmd_list): |
| 590 if ' ' in arg or '\t' in arg: |
| 591 arg = '"' + arg + '"' |
| 592 cl.append(arg) |
| 593 return ' '.join(cl) |
| 594 |
| 595 # A fiddlin' little function that has an 'import SCons.Environment' which |
| 596 # can't be moved to the top level without creating an import loop. Since |
| 597 # this import creates a local variable named 'SCons', it blocks access to |
| 598 # the global variable, so we move it here to prevent complaints about local |
| 599 # variables being used uninitialized. |
| 600 default_ENV = None |
| 601 def get_default_ENV(env): |
| 602 global default_ENV |
| 603 try: |
| 604 return env['ENV'] |
| 605 except KeyError: |
| 606 if not default_ENV: |
| 607 import SCons.Environment |
| 608 # This is a hideously expensive way to get a default shell |
| 609 # environment. What it really should do is run the platform |
| 610 # setup to get the default ENV. Fortunately, it's incredibly |
| 611 # rare for an Environment not to have a shell environment, so |
| 612 # we're not going to worry about it overmuch. |
| 613 default_ENV = SCons.Environment.Environment()['ENV'] |
| 614 return default_ENV |
| 615 |
| 616 # This function is still in draft mode. We're going to need something like |
| 617 # it in the long run as more and more places use subprocess, but I'm sure |
| 618 # it'll have to be tweaked to get the full desired functionality. |
| 619 # one special arg (so far?), 'error', to tell what to do with exceptions. |
| 620 def _subproc(scons_env, cmd, error = 'ignore', **kw): |
| 621 """Do common setup for a subprocess.Popen() call""" |
| 622 # allow std{in,out,err} to be "'devnull'" |
| 623 io = kw.get('stdin') |
| 624 if is_String(io) and io == 'devnull': |
| 625 kw['stdin'] = open(os.devnull) |
| 626 io = kw.get('stdout') |
| 627 if is_String(io) and io == 'devnull': |
| 628 kw['stdout'] = open(os.devnull, 'w') |
| 629 io = kw.get('stderr') |
| 630 if is_String(io) and io == 'devnull': |
| 631 kw['stderr'] = open(os.devnull, 'w') |
| 632 |
| 633 # Figure out what shell environment to use |
| 634 ENV = kw.get('env', None) |
| 635 if ENV is None: ENV = get_default_ENV(scons_env) |
| 636 |
| 637 # Ensure that the ENV values are all strings: |
| 638 new_env = {} |
| 639 for key, value in ENV.items(): |
| 640 if is_List(value): |
| 641 # If the value is a list, then we assume it is a path list, |
| 642 # because that's a pretty common list-like value to stick |
| 643 # in an environment variable: |
| 644 value = SCons.Util.flatten_sequence(value) |
| 645 new_env[key] = os.pathsep.join(map(str, value)) |
| 646 else: |
| 647 # It's either a string or something else. If it's a string, |
| 648 # we still want to call str() because it might be a *Unicode* |
| 649 # string, which makes subprocess.Popen() gag. If it isn't a |
| 650 # string or a list, then we just coerce it to a string, which |
| 651 # is the proper way to handle Dir and File instances and will |
| 652 # produce something reasonable for just about everything else: |
| 653 new_env[key] = str(value) |
| 654 kw['env'] = new_env |
| 655 |
| 656 try: |
| 657 #FUTURE return subprocess.Popen(cmd, **kw) |
| 658 return subprocess.Popen(cmd, **kw) |
| 659 except EnvironmentError, e: |
| 660 if error == 'raise': raise |
| 661 # return a dummy Popen instance that only returns error |
| 662 class dummyPopen(object): |
| 663 def __init__(self, e): self.exception = e |
| 664 def communicate(self): return ('','') |
| 665 def wait(self): return -self.exception.errno |
| 666 stdin = None |
| 667 class f(object): |
| 668 def read(self): return '' |
| 669 def readline(self): return '' |
| 670 stdout = stderr = f() |
| 671 return dummyPopen(e) |
| 672 |
| 673 class CommandAction(_ActionAction): |
| 674 """Class for command-execution actions.""" |
| 675 def __init__(self, cmd, **kw): |
| 676 # Cmd can actually be a list or a single item; if it's a |
| 677 # single item it should be the command string to execute; if a |
| 678 # list then it should be the words of the command string to |
| 679 # execute. Only a single command should be executed by this |
| 680 # object; lists of commands should be handled by embedding |
| 681 # these objects in a ListAction object (which the Action() |
| 682 # factory above does). cmd will be passed to |
| 683 # Environment.subst_list() for substituting environment |
| 684 # variables. |
| 685 if __debug__: logInstanceCreation(self, 'Action.CommandAction') |
| 686 |
| 687 _ActionAction.__init__(self, **kw) |
| 688 if is_List(cmd): |
| 689 if list(filter(is_List, cmd)): |
| 690 raise TypeError("CommandAction should be given only " \ |
| 691 "a single command") |
| 692 self.cmd_list = cmd |
| 693 |
| 694 def __str__(self): |
| 695 if is_List(self.cmd_list): |
| 696 return ' '.join(map(str, self.cmd_list)) |
| 697 return str(self.cmd_list) |
| 698 |
| 699 def process(self, target, source, env, executor=None): |
| 700 if executor: |
| 701 result = env.subst_list(self.cmd_list, 0, executor=executor) |
| 702 else: |
| 703 result = env.subst_list(self.cmd_list, 0, target, source) |
| 704 silent = None |
| 705 ignore = None |
| 706 while True: |
| 707 try: c = result[0][0][0] |
| 708 except IndexError: c = None |
| 709 if c == '@': silent = 1 |
| 710 elif c == '-': ignore = 1 |
| 711 else: break |
| 712 result[0][0] = result[0][0][1:] |
| 713 try: |
| 714 if not result[0][0]: |
| 715 result[0] = result[0][1:] |
| 716 except IndexError: |
| 717 pass |
| 718 return result, ignore, silent |
| 719 |
| 720 def strfunction(self, target, source, env, executor=None): |
| 721 if self.cmdstr is None: |
| 722 return None |
| 723 if self.cmdstr is not _null: |
| 724 from SCons.Subst import SUBST_RAW |
| 725 if executor: |
| 726 c = env.subst(self.cmdstr, SUBST_RAW, executor=executor) |
| 727 else: |
| 728 c = env.subst(self.cmdstr, SUBST_RAW, target, source) |
| 729 if c: |
| 730 return c |
| 731 cmd_list, ignore, silent = self.process(target, source, env, executor) |
| 732 if silent: |
| 733 return '' |
| 734 return _string_from_cmd_list(cmd_list[0]) |
| 735 |
| 736 def execute(self, target, source, env, executor=None): |
| 737 """Execute a command action. |
| 738 |
| 739 This will handle lists of commands as well as individual commands, |
| 740 because construction variable substitution may turn a single |
| 741 "command" into a list. This means that this class can actually |
| 742 handle lists of commands, even though that's not how we use it |
| 743 externally. |
| 744 """ |
| 745 escape_list = SCons.Subst.escape_list |
| 746 flatten_sequence = SCons.Util.flatten_sequence |
| 747 |
| 748 try: |
| 749 shell = env['SHELL'] |
| 750 except KeyError: |
| 751 raise SCons.Errors.UserError('Missing SHELL construction variable.') |
| 752 |
| 753 try: |
| 754 spawn = env['SPAWN'] |
| 755 except KeyError: |
| 756 raise SCons.Errors.UserError('Missing SPAWN construction variable.') |
| 757 else: |
| 758 if is_String(spawn): |
| 759 spawn = env.subst(spawn, raw=1, conv=lambda x: x) |
| 760 |
| 761 escape = env.get('ESCAPE', lambda x: x) |
| 762 |
| 763 ENV = get_default_ENV(env) |
| 764 |
| 765 # Ensure that the ENV values are all strings: |
| 766 for key, value in ENV.items(): |
| 767 if not is_String(value): |
| 768 if is_List(value): |
| 769 # If the value is a list, then we assume it is a |
| 770 # path list, because that's a pretty common list-like |
| 771 # value to stick in an environment variable: |
| 772 value = flatten_sequence(value) |
| 773 ENV[key] = os.pathsep.join(map(str, value)) |
| 774 else: |
| 775 # If it isn't a string or a list, then we just coerce |
| 776 # it to a string, which is the proper way to handle |
| 777 # Dir and File instances and will produce something |
| 778 # reasonable for just about everything else: |
| 779 ENV[key] = str(value) |
| 780 |
| 781 if executor: |
| 782 target = executor.get_all_targets() |
| 783 source = executor.get_all_sources() |
| 784 cmd_list, ignore, silent = self.process(target, list(map(rfile, source))
, env, executor) |
| 785 |
| 786 # Use len() to filter out any "command" that's zero-length. |
| 787 for cmd_line in filter(len, cmd_list): |
| 788 # Escape the command line for the interpreter we are using. |
| 789 cmd_line = escape_list(cmd_line, escape) |
| 790 result = spawn(shell, escape, cmd_line[0], cmd_line, ENV) |
| 791 if not ignore and result: |
| 792 msg = "Error %s" % result |
| 793 return SCons.Errors.BuildError(errstr=msg, |
| 794 status=result, |
| 795 action=self, |
| 796 command=cmd_line) |
| 797 return 0 |
| 798 |
| 799 def get_presig(self, target, source, env, executor=None): |
| 800 """Return the signature contents of this action's command line. |
| 801 |
| 802 This strips $(-$) and everything in between the string, |
| 803 since those parts don't affect signatures. |
| 804 """ |
| 805 from SCons.Subst import SUBST_SIG |
| 806 cmd = self.cmd_list |
| 807 if is_List(cmd): |
| 808 cmd = ' '.join(map(str, cmd)) |
| 809 else: |
| 810 cmd = str(cmd) |
| 811 if executor: |
| 812 return env.subst_target_source(cmd, SUBST_SIG, executor=executor) |
| 813 else: |
| 814 return env.subst_target_source(cmd, SUBST_SIG, target, source) |
| 815 |
| 816 def get_implicit_deps(self, target, source, env, executor=None): |
| 817 icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True) |
| 818 if is_String(icd) and icd[:1] == '$': |
| 819 icd = env.subst(icd) |
| 820 if not icd or icd in ('0', 'None'): |
| 821 return [] |
| 822 from SCons.Subst import SUBST_SIG |
| 823 if executor: |
| 824 cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, executor=executo
r) |
| 825 else: |
| 826 cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, target, source) |
| 827 res = [] |
| 828 for cmd_line in cmd_list: |
| 829 if cmd_line: |
| 830 d = str(cmd_line[0]) |
| 831 m = strip_quotes.match(d) |
| 832 if m: |
| 833 d = m.group(1) |
| 834 d = env.WhereIs(d) |
| 835 if d: |
| 836 res.append(env.fs.File(d)) |
| 837 return res |
| 838 |
| 839 class CommandGeneratorAction(ActionBase): |
| 840 """Class for command-generator actions.""" |
| 841 def __init__(self, generator, kw): |
| 842 if __debug__: logInstanceCreation(self, 'Action.CommandGeneratorAction') |
| 843 self.generator = generator |
| 844 self.gen_kw = kw |
| 845 self.varlist = kw.get('varlist', ()) |
| 846 self.targets = kw.get('targets', '$TARGETS') |
| 847 |
| 848 def _generate(self, target, source, env, for_signature, executor=None): |
| 849 # ensure that target is a list, to make it easier to write |
| 850 # generator functions: |
| 851 if not is_List(target): |
| 852 target = [target] |
| 853 |
| 854 if executor: |
| 855 target = executor.get_all_targets() |
| 856 source = executor.get_all_sources() |
| 857 ret = self.generator(target=target, |
| 858 source=source, |
| 859 env=env, |
| 860 for_signature=for_signature) |
| 861 gen_cmd = Action(ret, **self.gen_kw) |
| 862 if not gen_cmd: |
| 863 raise SCons.Errors.UserError("Object returned from command generator
: %s cannot be used to create an Action." % repr(ret)) |
| 864 return gen_cmd |
| 865 |
| 866 def __str__(self): |
| 867 try: |
| 868 env = self.presub_env |
| 869 except AttributeError: |
| 870 env = None |
| 871 if env is None: |
| 872 env = SCons.Defaults.DefaultEnvironment() |
| 873 act = self._generate([], [], env, 1) |
| 874 return str(act) |
| 875 |
| 876 def batch_key(self, env, target, source): |
| 877 return self._generate(target, source, env, 1).batch_key(env, target, sou
rce) |
| 878 |
| 879 def genstring(self, target, source, env, executor=None): |
| 880 return self._generate(target, source, env, 1, executor).genstring(target
, source, env) |
| 881 |
| 882 def __call__(self, target, source, env, exitstatfunc=_null, presub=_null, |
| 883 show=_null, execute=_null, chdir=_null, executor=None): |
| 884 act = self._generate(target, source, env, 0, executor) |
| 885 if act is None: |
| 886 raise UserError("While building `%s': " |
| 887 "Cannot deduce file extension from source files: %s" |
| 888 % (repr(list(map(str, target))), repr(list(map(str, source))))) |
| 889 return act(target, source, env, exitstatfunc, presub, |
| 890 show, execute, chdir, executor) |
| 891 |
| 892 def get_presig(self, target, source, env, executor=None): |
| 893 """Return the signature contents of this action's command line. |
| 894 |
| 895 This strips $(-$) and everything in between the string, |
| 896 since those parts don't affect signatures. |
| 897 """ |
| 898 return self._generate(target, source, env, 1, executor).get_presig(targe
t, source, env) |
| 899 |
| 900 def get_implicit_deps(self, target, source, env, executor=None): |
| 901 return self._generate(target, source, env, 1, executor).get_implicit_dep
s(target, source, env) |
| 902 |
| 903 def get_varlist(self, target, source, env, executor=None): |
| 904 return self._generate(target, source, env, 1, executor).get_varlist(targ
et, source, env, executor) |
| 905 |
| 906 def get_targets(self, env, executor): |
| 907 return self._generate(None, None, env, 1, executor).get_targets(env, exe
cutor) |
| 908 |
| 909 |
| 910 |
| 911 # A LazyAction is a kind of hybrid generator and command action for |
| 912 # strings of the form "$VAR". These strings normally expand to other |
| 913 # strings (think "$CCCOM" to "$CC -c -o $TARGET $SOURCE"), but we also |
| 914 # want to be able to replace them with functions in the construction |
| 915 # environment. Consequently, we want lazy evaluation and creation of |
| 916 # an Action in the case of the function, but that's overkill in the more |
| 917 # normal case of expansion to other strings. |
| 918 # |
| 919 # So we do this with a subclass that's both a generator *and* |
| 920 # a command action. The overridden methods all do a quick check |
| 921 # of the construction variable, and if it's a string we just call |
| 922 # the corresponding CommandAction method to do the heavy lifting. |
| 923 # If not, then we call the same-named CommandGeneratorAction method. |
| 924 # The CommandGeneratorAction methods work by using the overridden |
| 925 # _generate() method, that is, our own way of handling "generation" of |
| 926 # an action based on what's in the construction variable. |
| 927 |
| 928 class LazyAction(CommandGeneratorAction, CommandAction): |
| 929 |
| 930 def __init__(self, var, kw): |
| 931 if __debug__: logInstanceCreation(self, 'Action.LazyAction') |
| 932 #FUTURE CommandAction.__init__(self, '${'+var+'}', **kw) |
| 933 CommandAction.__init__(self, '${'+var+'}', **kw) |
| 934 self.var = SCons.Util.to_String(var) |
| 935 self.gen_kw = kw |
| 936 |
| 937 def get_parent_class(self, env): |
| 938 c = env.get(self.var) |
| 939 if is_String(c) and not '\n' in c: |
| 940 return CommandAction |
| 941 return CommandGeneratorAction |
| 942 |
| 943 def _generate_cache(self, env): |
| 944 if env: |
| 945 c = env.get(self.var, '') |
| 946 else: |
| 947 c = '' |
| 948 gen_cmd = Action(c, **self.gen_kw) |
| 949 if not gen_cmd: |
| 950 raise SCons.Errors.UserError("$%s value %s cannot be used to create
an Action." % (self.var, repr(c))) |
| 951 return gen_cmd |
| 952 |
| 953 def _generate(self, target, source, env, for_signature, executor=None): |
| 954 return self._generate_cache(env) |
| 955 |
| 956 def __call__(self, target, source, env, *args, **kw): |
| 957 c = self.get_parent_class(env) |
| 958 return c.__call__(self, target, source, env, *args, **kw) |
| 959 |
| 960 def get_presig(self, target, source, env): |
| 961 c = self.get_parent_class(env) |
| 962 return c.get_presig(self, target, source, env) |
| 963 |
| 964 def get_varlist(self, target, source, env, executor=None): |
| 965 c = self.get_parent_class(env) |
| 966 return c.get_varlist(self, target, source, env, executor) |
| 967 |
| 968 |
| 969 class FunctionAction(_ActionAction): |
| 970 """Class for Python function actions.""" |
| 971 |
| 972 def __init__(self, execfunction, kw): |
| 973 if __debug__: logInstanceCreation(self, 'Action.FunctionAction') |
| 974 |
| 975 self.execfunction = execfunction |
| 976 try: |
| 977 self.funccontents = _callable_contents(execfunction) |
| 978 except AttributeError: |
| 979 try: |
| 980 # See if execfunction will do the heavy lifting for us. |
| 981 self.gc = execfunction.get_contents |
| 982 except AttributeError: |
| 983 # This is weird, just do the best we can. |
| 984 self.funccontents = _object_contents(execfunction) |
| 985 |
| 986 _ActionAction.__init__(self, **kw) |
| 987 |
| 988 def function_name(self): |
| 989 try: |
| 990 return self.execfunction.__name__ |
| 991 except AttributeError: |
| 992 try: |
| 993 return self.execfunction.__class__.__name__ |
| 994 except AttributeError: |
| 995 return "unknown_python_function" |
| 996 |
| 997 def strfunction(self, target, source, env, executor=None): |
| 998 if self.cmdstr is None: |
| 999 return None |
| 1000 if self.cmdstr is not _null: |
| 1001 from SCons.Subst import SUBST_RAW |
| 1002 if executor: |
| 1003 c = env.subst(self.cmdstr, SUBST_RAW, executor=executor) |
| 1004 else: |
| 1005 c = env.subst(self.cmdstr, SUBST_RAW, target, source) |
| 1006 if c: |
| 1007 return c |
| 1008 def array(a): |
| 1009 def quote(s): |
| 1010 try: |
| 1011 str_for_display = s.str_for_display |
| 1012 except AttributeError: |
| 1013 s = repr(s) |
| 1014 else: |
| 1015 s = str_for_display() |
| 1016 return s |
| 1017 return '[' + ", ".join(map(quote, a)) + ']' |
| 1018 try: |
| 1019 strfunc = self.execfunction.strfunction |
| 1020 except AttributeError: |
| 1021 pass |
| 1022 else: |
| 1023 if strfunc is None: |
| 1024 return None |
| 1025 if callable(strfunc): |
| 1026 return strfunc(target, source, env) |
| 1027 name = self.function_name() |
| 1028 tstr = array(target) |
| 1029 sstr = array(source) |
| 1030 return "%s(%s, %s)" % (name, tstr, sstr) |
| 1031 |
| 1032 def __str__(self): |
| 1033 name = self.function_name() |
| 1034 if name == 'ActionCaller': |
| 1035 return str(self.execfunction) |
| 1036 return "%s(target, source, env)" % name |
| 1037 |
| 1038 def execute(self, target, source, env, executor=None): |
| 1039 exc_info = (None,None,None) |
| 1040 try: |
| 1041 if executor: |
| 1042 target = executor.get_all_targets() |
| 1043 source = executor.get_all_sources() |
| 1044 rsources = list(map(rfile, source)) |
| 1045 try: |
| 1046 result = self.execfunction(target=target, source=rsources, env=e
nv) |
| 1047 except KeyboardInterrupt, e: |
| 1048 raise |
| 1049 except SystemExit, e: |
| 1050 raise |
| 1051 except Exception, e: |
| 1052 result = e |
| 1053 exc_info = sys.exc_info() |
| 1054 |
| 1055 if result: |
| 1056 result = SCons.Errors.convert_to_BuildError(result, exc_info) |
| 1057 result.node=target |
| 1058 result.action=self |
| 1059 try: |
| 1060 result.command=self.strfunction(target, source, env, executo
r) |
| 1061 except TypeError: |
| 1062 result.command=self.strfunction(target, source, env) |
| 1063 |
| 1064 # FIXME: This maintains backward compatibility with respect to |
| 1065 # which type of exceptions were returned by raising an |
| 1066 # exception and which ones were returned by value. It would |
| 1067 # probably be best to always return them by value here, but |
| 1068 # some codes do not check the return value of Actions and I do |
| 1069 # not have the time to modify them at this point. |
| 1070 if (exc_info[1] and |
| 1071 not isinstance(exc_info[1],EnvironmentError)): |
| 1072 raise result |
| 1073 |
| 1074 return result |
| 1075 finally: |
| 1076 # Break the cycle between the traceback object and this |
| 1077 # function stack frame. See the sys.exc_info() doc info for |
| 1078 # more information about this issue. |
| 1079 del exc_info |
| 1080 |
| 1081 |
| 1082 def get_presig(self, target, source, env): |
| 1083 """Return the signature contents of this callable action.""" |
| 1084 try: |
| 1085 return self.gc(target, source, env) |
| 1086 except AttributeError: |
| 1087 return self.funccontents |
| 1088 |
| 1089 def get_implicit_deps(self, target, source, env): |
| 1090 return [] |
| 1091 |
| 1092 class ListAction(ActionBase): |
| 1093 """Class for lists of other actions.""" |
| 1094 def __init__(self, actionlist): |
| 1095 if __debug__: logInstanceCreation(self, 'Action.ListAction') |
| 1096 def list_of_actions(x): |
| 1097 if isinstance(x, ActionBase): |
| 1098 return x |
| 1099 return Action(x) |
| 1100 self.list = list(map(list_of_actions, actionlist)) |
| 1101 # our children will have had any varlist |
| 1102 # applied; we don't need to do it again |
| 1103 self.varlist = () |
| 1104 self.targets = '$TARGETS' |
| 1105 |
| 1106 def genstring(self, target, source, env): |
| 1107 return '\n'.join([a.genstring(target, source, env) for a in self.list]) |
| 1108 |
| 1109 def __str__(self): |
| 1110 return '\n'.join(map(str, self.list)) |
| 1111 |
| 1112 def presub_lines(self, env): |
| 1113 return SCons.Util.flatten_sequence( |
| 1114 [a.presub_lines(env) for a in self.list]) |
| 1115 |
| 1116 def get_presig(self, target, source, env): |
| 1117 """Return the signature contents of this action list. |
| 1118 |
| 1119 Simple concatenation of the signatures of the elements. |
| 1120 """ |
| 1121 return "".join([x.get_contents(target, source, env) for x in self.list]) |
| 1122 |
| 1123 def __call__(self, target, source, env, exitstatfunc=_null, presub=_null, |
| 1124 show=_null, execute=_null, chdir=_null, executor=None): |
| 1125 if executor: |
| 1126 target = executor.get_all_targets() |
| 1127 source = executor.get_all_sources() |
| 1128 for act in self.list: |
| 1129 stat = act(target, source, env, exitstatfunc, presub, |
| 1130 show, execute, chdir, executor) |
| 1131 if stat: |
| 1132 return stat |
| 1133 return 0 |
| 1134 |
| 1135 def get_implicit_deps(self, target, source, env): |
| 1136 result = [] |
| 1137 for act in self.list: |
| 1138 result.extend(act.get_implicit_deps(target, source, env)) |
| 1139 return result |
| 1140 |
| 1141 def get_varlist(self, target, source, env, executor=None): |
| 1142 result = SCons.Util.OrderedDict() |
| 1143 for act in self.list: |
| 1144 for var in act.get_varlist(target, source, env, executor): |
| 1145 result[var] = True |
| 1146 return list(result.keys()) |
| 1147 |
| 1148 class ActionCaller(object): |
| 1149 """A class for delaying calling an Action function with specific |
| 1150 (positional and keyword) arguments until the Action is actually |
| 1151 executed. |
| 1152 |
| 1153 This class looks to the rest of the world like a normal Action object, |
| 1154 but what it's really doing is hanging on to the arguments until we |
| 1155 have a target, source and env to use for the expansion. |
| 1156 """ |
| 1157 def __init__(self, parent, args, kw): |
| 1158 self.parent = parent |
| 1159 self.args = args |
| 1160 self.kw = kw |
| 1161 |
| 1162 def get_contents(self, target, source, env): |
| 1163 actfunc = self.parent.actfunc |
| 1164 try: |
| 1165 # "self.actfunc" is a function. |
| 1166 contents = str(actfunc.func_code.co_code) |
| 1167 except AttributeError: |
| 1168 # "self.actfunc" is a callable object. |
| 1169 try: |
| 1170 contents = str(actfunc.__call__.im_func.func_code.co_code) |
| 1171 except AttributeError: |
| 1172 # No __call__() method, so it might be a builtin |
| 1173 # or something like that. Do the best we can. |
| 1174 contents = str(actfunc) |
| 1175 contents = remove_set_lineno_codes(contents) |
| 1176 return contents |
| 1177 |
| 1178 def subst(self, s, target, source, env): |
| 1179 # If s is a list, recursively apply subst() |
| 1180 # to every element in the list |
| 1181 if is_List(s): |
| 1182 result = [] |
| 1183 for elem in s: |
| 1184 result.append(self.subst(elem, target, source, env)) |
| 1185 return self.parent.convert(result) |
| 1186 |
| 1187 # Special-case hack: Let a custom function wrapped in an |
| 1188 # ActionCaller get at the environment through which the action |
| 1189 # was called by using this hard-coded value as a special return. |
| 1190 if s == '$__env__': |
| 1191 return env |
| 1192 elif is_String(s): |
| 1193 return env.subst(s, 1, target, source) |
| 1194 return self.parent.convert(s) |
| 1195 |
| 1196 def subst_args(self, target, source, env): |
| 1197 return [self.subst(x, target, source, env) for x in self.args] |
| 1198 |
| 1199 def subst_kw(self, target, source, env): |
| 1200 kw = {} |
| 1201 for key in self.kw.keys(): |
| 1202 kw[key] = self.subst(self.kw[key], target, source, env) |
| 1203 return kw |
| 1204 |
| 1205 def __call__(self, target, source, env, executor=None): |
| 1206 args = self.subst_args(target, source, env) |
| 1207 kw = self.subst_kw(target, source, env) |
| 1208 return self.parent.actfunc(*args, **kw) |
| 1209 |
| 1210 def strfunction(self, target, source, env): |
| 1211 args = self.subst_args(target, source, env) |
| 1212 kw = self.subst_kw(target, source, env) |
| 1213 return self.parent.strfunc(*args, **kw) |
| 1214 |
| 1215 def __str__(self): |
| 1216 return self.parent.strfunc(*self.args, **self.kw) |
| 1217 |
| 1218 class ActionFactory(object): |
| 1219 """A factory class that will wrap up an arbitrary function |
| 1220 as an SCons-executable Action object. |
| 1221 |
| 1222 The real heavy lifting here is done by the ActionCaller class. |
| 1223 We just collect the (positional and keyword) arguments that we're |
| 1224 called with and give them to the ActionCaller object we create, |
| 1225 so it can hang onto them until it needs them. |
| 1226 """ |
| 1227 def __init__(self, actfunc, strfunc, convert=lambda x: x): |
| 1228 self.actfunc = actfunc |
| 1229 self.strfunc = strfunc |
| 1230 self.convert = convert |
| 1231 |
| 1232 def __call__(self, *args, **kw): |
| 1233 ac = ActionCaller(self, args, kw) |
| 1234 action = Action(ac, strfunction=ac.strfunction) |
| 1235 return action |
| 1236 |
| 1237 # Local Variables: |
| 1238 # tab-width:4 |
| 1239 # indent-tabs-mode:nil |
| 1240 # End: |
| 1241 # vim: set expandtab tabstop=4 shiftwidth=4: |
OLD | NEW |