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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 __revision__ = "src/engine/SCons/Action.py rel_2.5.1:3735:9dc6cee5c168 2016/11/03 14:02:02 bdbaddog"
101
102 import dis
103 import os
104
105 import pickle
106 import re
107 import sys
108 import subprocess
109
110 import SCons.Debug
111 from SCons.Debug import logInstanceCreation
112 import SCons.Errors
113 import SCons.Util
114 import SCons.Subst
115
116
117 is_String = SCons.Util.is_String
118 is_List = SCons.Util.is_List
119
122
123 print_actions = 1
124 execute_actions = 1
125 print_actions_presub = 0
126
128 try:
129 return n.rfile()
130 except AttributeError:
131 return n
132
135
136 try:
137 SET_LINENO = dis.SET_LINENO
138 HAVE_ARGUMENT = dis.HAVE_ARGUMENT
139 except AttributeError:
140 remove_set_lineno_codes = lambda x: x
141 else:
157
158 strip_quotes = re.compile('^[\'"](.*)[\'"]$')
159
160
162 """Return the signature contents of a callable Python object.
163 """
164 try:
165
166 return _function_contents(obj.im_func)
167
168 except AttributeError:
169 try:
170
171 return _function_contents(obj.__call__.im_func)
172
173 except AttributeError:
174 try:
175
176 return _code_contents(obj)
177
178 except AttributeError:
179
180 return _function_contents(obj)
181
182
184 """Return the signature contents of any Python object.
185
186 We have to handle the case where object contains a code object
187 since it can be pickled directly.
188 """
189 try:
190
191 return _function_contents(obj.im_func)
192
193 except AttributeError:
194 try:
195
196 return _function_contents(obj.__call__.im_func)
197
198 except AttributeError:
199 try:
200
201 return _code_contents(obj)
202
203 except AttributeError:
204 try:
205
206 return _function_contents(obj)
207
208 except AttributeError:
209
210 try:
211 return pickle.dumps(obj)
212 except (pickle.PicklingError, TypeError):
213
214
215
216
217
218 return str(obj)
219
220
221 -def _code_contents(code):
222 """Return the signature contents of a code object.
223
224 By providing direct access to the code object of the
225 function, Python makes this extremely easy. Hooray!
226
227 Unfortunately, older versions of Python include line
228 number indications in the compiled byte code. Boo!
229 So we remove the line number byte codes to prevent
230 recompilations from moving a Python function.
231 """
232
233 contents = []
234
235
236
237 contents.append("%s,%s" % (code.co_argcount, len(code.co_varnames)))
238 contents.append(",%s,%s" % (len(code.co_cellvars), len(code.co_freevars)))
239
240
241
242
243
244
245
246
247
248 contents.append(',(' + ','.join(map(_object_contents,code.co_consts[1:])) + ')')
249
250
251
252
253
254 contents.append(',(' + ','.join(map(_object_contents,code.co_names)) + ')')
255
256
257
258 contents.append(',(' + str(remove_set_lineno_codes(code.co_code)) + ')')
259
260 return ''.join(contents)
261
262
264 """Return the signature contents of a function."""
265
266 contents = [_code_contents(func.func_code)]
267
268
269 if func.func_defaults:
270 contents.append(',(' + ','.join(map(_object_contents,func.func_defaults)) + ')')
271 else:
272 contents.append(',()')
273
274
275 closure = func.func_closure or []
276
277
278 try:
279 xxx = [_object_contents(x.cell_contents) for x in closure]
280 except AttributeError:
281 xxx = []
282 contents.append(',(' + ','.join(xxx) + ')')
283
284 return ''.join(contents)
285
286
288
289
290
291 a1 = Action(act1)
292 a2 = Action(act2)
293 if a1 is None:
294 return a2
295 if a2 is None:
296 return a1
297 if isinstance(a1, ListAction):
298 if isinstance(a2, ListAction):
299 return ListAction(a1.list + a2.list)
300 else:
301 return ListAction(a1.list + [ a2 ])
302 else:
303 if isinstance(a2, ListAction):
304 return ListAction([ a1 ] + a2.list)
305 else:
306 return ListAction([ a1, a2 ])
307
309 """This converts any arguments after the action argument into
310 their equivalent keywords and adds them to the kw argument.
311 """
312 v = kw.get('varlist', ())
313
314 if is_String(v): v = (v,)
315 kw['varlist'] = tuple(v)
316 if args:
317
318 cmdstrfunc = args[0]
319 if cmdstrfunc is None or is_String(cmdstrfunc):
320 kw['cmdstr'] = cmdstrfunc
321 elif callable(cmdstrfunc):
322 kw['strfunction'] = cmdstrfunc
323 else:
324 raise SCons.Errors.UserError(
325 'Invalid command display variable type. '
326 'You must either pass a string or a callback which '
327 'accepts (target, source, env) as parameters.')
328 if len(args) > 1:
329 kw['varlist'] = tuple(SCons.Util.flatten(args[1:])) + kw['varlist']
330 if kw.get('strfunction', _null) is not _null \
331 and kw.get('cmdstr', _null) is not _null:
332 raise SCons.Errors.UserError(
333 'Cannot have both strfunction and cmdstr args to Action()')
334
336 """This is the actual "implementation" for the
337 Action factory method, below. This handles the
338 fact that passing lists to Action() itself has
339 different semantics than passing lists as elements
340 of lists.
341
342 The former will create a ListAction, the latter
343 will create a CommandAction by converting the inner
344 list elements to strings."""
345
346 if isinstance(act, ActionBase):
347 return act
348
349 if is_String(act):
350 var=SCons.Util.get_environment_var(act)
351 if var:
352
353
354
355
356
357
358 return LazyAction(var, kw)
359 commands = str(act).split('\n')
360 if len(commands) == 1:
361 return CommandAction(commands[0], **kw)
362
363
364 return _do_create_list_action(commands, kw)
365
366 if is_List(act):
367 return CommandAction(act, **kw)
368
369 if callable(act):
370 try:
371 gen = kw['generator']
372 del kw['generator']
373 except KeyError:
374 gen = 0
375 if gen:
376 action_type = CommandGeneratorAction
377 else:
378 action_type = FunctionAction
379 return action_type(act, kw)
380
381
382 if isinstance(act, int) or isinstance(act, float):
383 raise TypeError("Don't know how to create an Action from a number (%s)"%act)
384
385 return None
386
388 """A factory for list actions. Convert the input list into Actions
389 and then wrap them in a ListAction."""
390 acts = []
391 for a in act:
392 aa = _do_create_action(a, kw)
393 if aa is not None: acts.append(aa)
394 if not acts:
395 return ListAction([])
396 elif len(acts) == 1:
397 return acts[0]
398 else:
399 return ListAction(acts)
400
408
410 """Base class for all types of action objects that can be held by
411 other objects (Builders, Executors, etc.) This provides the
412 common methods for manipulating and combining those actions."""
413
415 return cmp(self.__dict__, other)
416
419
420 batch_key = no_batch_key
421
424
425 - def get_contents(self, target, source, env):
426 result = [ self.get_presig(target, source, env) ]
427
428
429
430 vl = self.get_varlist(target, source, env)
431 if is_String(vl): vl = (vl,)
432 for v in vl:
433
434 result.append(env.subst_target_source('${'+v+'}', SCons.Subst.SUBST_SIG, target, source))
435 return ''.join(result)
436
439
442
444
445
446
447
448
449
450 self.presub_env = env
451 lines = str(self).split('\n')
452 self.presub_env = None
453 return lines
454
455 - def get_varlist(self, target, source, env, executor=None):
457
459 """
460 Returns the type of targets ($TARGETS, $CHANGED_TARGETS) used
461 by this action.
462 """
463 return self.targets
464
466 """Base class for actions that create output objects."""
467 - def __init__(self, cmdstr=_null, strfunction=_null, varlist=(),
468 presub=_null, chdir=None, exitstatfunc=None,
469 batch_key=None, targets='$TARGETS',
470 **kw):
494 batch_key = default_batch_key
495 SCons.Util.AddMethod(self, batch_key, 'batch_key')
496
498
499
500
501
502
503
504
505
506 try:
507 sys.stdout.write(unicode(s + "\n"))
508 except UnicodeDecodeError:
509 sys.stdout.write(s + "\n")
510
518 if not is_List(target):
519 target = [target]
520 if not is_List(source):
521 source = [source]
522
523 if presub is _null:
524 presub = self.presub
525 if presub is _null:
526 presub = print_actions_presub
527 if exitstatfunc is _null: exitstatfunc = self.exitstatfunc
528 if show is _null: show = print_actions
529 if execute is _null: execute = execute_actions
530 if chdir is _null: chdir = self.chdir
531 save_cwd = None
532 if chdir:
533 save_cwd = os.getcwd()
534 try:
535 chdir = str(chdir.get_abspath())
536 except AttributeError:
537 if not is_String(chdir):
538 if executor:
539 chdir = str(executor.batches[0].targets[0].dir)
540 else:
541 chdir = str(target[0].dir)
542 if presub:
543 if executor:
544 target = executor.get_all_targets()
545 source = executor.get_all_sources()
546 t = ' and '.join(map(str, target))
547 l = '\n '.join(self.presub_lines(env))
548 out = u"Building %s with action:\n %s\n" % (t, l)
549 sys.stdout.write(out)
550 cmd = None
551 if show and self.strfunction:
552 if executor:
553 target = executor.get_all_targets()
554 source = executor.get_all_sources()
555 try:
556 cmd = self.strfunction(target, source, env, executor)
557 except TypeError:
558 cmd = self.strfunction(target, source, env)
559 if cmd:
560 if chdir:
561 cmd = ('os.chdir(%s)\n' % repr(chdir)) + cmd
562 try:
563 get = env.get
564 except AttributeError:
565 print_func = self.print_cmd_line
566 else:
567 print_func = get('PRINT_CMD_LINE_FUNC')
568 if not print_func:
569 print_func = self.print_cmd_line
570 print_func(cmd, target, source, env)
571 stat = 0
572 if execute:
573 if chdir:
574 os.chdir(chdir)
575 try:
576 stat = self.execute(target, source, env, executor=executor)
577 if isinstance(stat, SCons.Errors.BuildError):
578 s = exitstatfunc(stat.status)
579 if s:
580 stat.status = s
581 else:
582 stat = s
583 else:
584 stat = exitstatfunc(stat)
585 finally:
586 if save_cwd:
587 os.chdir(save_cwd)
588 if cmd and save_cwd:
589 print_func('os.chdir(%s)' % repr(save_cwd), target, source, env)
590
591 return stat
592
593
595 """Takes a list of command line arguments and returns a pretty
596 representation for printing."""
597 cl = []
598 for arg in map(str, cmd_list):
599 if ' ' in arg or '\t' in arg:
600 arg = '"' + arg + '"'
601 cl.append(arg)
602 return ' '.join(cl)
603
604
605
606
607
608
609 default_ENV = None
624
625
626
627
628
629 -def _subproc(scons_env, cmd, error = 'ignore', **kw):
630 """Do common setup for a subprocess.Popen() call"""
631
632 io = kw.get('stdin')
633 if is_String(io) and io == 'devnull':
634 kw['stdin'] = open(os.devnull)
635 io = kw.get('stdout')
636 if is_String(io) and io == 'devnull':
637 kw['stdout'] = open(os.devnull, 'w')
638 io = kw.get('stderr')
639 if is_String(io) and io == 'devnull':
640 kw['stderr'] = open(os.devnull, 'w')
641
642
643 ENV = kw.get('env', None)
644 if ENV is None: ENV = get_default_ENV(scons_env)
645
646
647 new_env = {}
648 for key, value in ENV.items():
649 if is_List(value):
650
651
652
653 value = SCons.Util.flatten_sequence(value)
654 new_env[key] = os.pathsep.join(map(str, value))
655 else:
656
657
658
659
660
661
662 new_env[key] = str(value)
663 kw['env'] = new_env
664
665 try:
666 return subprocess.Popen(cmd, **kw)
667 except EnvironmentError, e:
668 if error == 'raise': raise
669
670 class dummyPopen(object):
671 def __init__(self, e): self.exception = e
672 def communicate(self,input=None): return ('','')
673 def wait(self): return -self.exception.errno
674 stdin = None
675 class f(object):
676 def read(self): return ''
677 def readline(self): return ''
678 def __iter__(self): return iter(())
679 stdout = stderr = f()
680 return dummyPopen(e)
681
683 """Class for command-execution actions."""
685
686
687
688
689
690
691
692
693
694 if SCons.Debug.track_instances: logInstanceCreation(self, 'Action.CommandAction')
695
696 _ActionAction.__init__(self, **kw)
697 if is_List(cmd):
698 if list(filter(is_List, cmd)):
699 raise TypeError("CommandAction should be given only " \
700 "a single command")
701 self.cmd_list = cmd
702
704 if is_List(self.cmd_list):
705 return ' '.join(map(str, self.cmd_list))
706 return str(self.cmd_list)
707
708 - def process(self, target, source, env, executor=None):
728
729 - def strfunction(self, target, source, env, executor=None):
730 if self.cmdstr is None:
731 return None
732 if self.cmdstr is not _null:
733 from SCons.Subst import SUBST_RAW
734 if executor:
735 c = env.subst(self.cmdstr, SUBST_RAW, executor=executor)
736 else:
737 c = env.subst(self.cmdstr, SUBST_RAW, target, source)
738 if c:
739 return c
740 cmd_list, ignore, silent = self.process(target, source, env, executor)
741 if silent:
742 return ''
743 return _string_from_cmd_list(cmd_list[0])
744
745 - def execute(self, target, source, env, executor=None):
746 """Execute a command action.
747
748 This will handle lists of commands as well as individual commands,
749 because construction variable substitution may turn a single
750 "command" into a list. This means that this class can actually
751 handle lists of commands, even though that's not how we use it
752 externally.
753 """
754 escape_list = SCons.Subst.escape_list
755 flatten_sequence = SCons.Util.flatten_sequence
756
757 try:
758 shell = env['SHELL']
759 except KeyError:
760 raise SCons.Errors.UserError('Missing SHELL construction variable.')
761
762 try:
763 spawn = env['SPAWN']
764 except KeyError:
765 raise SCons.Errors.UserError('Missing SPAWN construction variable.')
766 else:
767 if is_String(spawn):
768 spawn = env.subst(spawn, raw=1, conv=lambda x: x)
769
770 escape = env.get('ESCAPE', lambda x: x)
771
772 ENV = get_default_ENV(env)
773
774
775 for key, value in ENV.items():
776 if not is_String(value):
777 if is_List(value):
778
779
780
781 value = flatten_sequence(value)
782 ENV[key] = os.pathsep.join(map(str, value))
783 else:
784
785
786
787
788 ENV[key] = str(value)
789
790 if executor:
791 target = executor.get_all_targets()
792 source = executor.get_all_sources()
793 cmd_list, ignore, silent = self.process(target, list(map(rfile, source)), env, executor)
794
795
796 for cmd_line in filter(len, cmd_list):
797
798 cmd_line = escape_list(cmd_line, escape)
799 result = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
800 if not ignore and result:
801 msg = "Error %s" % result
802 return SCons.Errors.BuildError(errstr=msg,
803 status=result,
804 action=self,
805 command=cmd_line)
806 return 0
807
808 - def get_presig(self, target, source, env, executor=None):
809 """Return the signature contents of this action's command line.
810
811 This strips $(-$) and everything in between the string,
812 since those parts don't affect signatures.
813 """
814 from SCons.Subst import SUBST_SIG
815 cmd = self.cmd_list
816 if is_List(cmd):
817 cmd = ' '.join(map(str, cmd))
818 else:
819 cmd = str(cmd)
820 if executor:
821 return env.subst_target_source(cmd, SUBST_SIG, executor=executor)
822 else:
823 return env.subst_target_source(cmd, SUBST_SIG, target, source)
824
847
849 """Class for command-generator actions."""
856
857 - def _generate(self, target, source, env, for_signature, executor=None):
874
876 try:
877 env = self.presub_env
878 except AttributeError:
879 env = None
880 if env is None:
881 env = SCons.Defaults.DefaultEnvironment()
882 act = self._generate([], [], env, 1)
883 return str(act)
884
887
888 - def genstring(self, target, source, env, executor=None):
890
893 act = self._generate(target, source, env, 0, executor)
894 if act is None:
895 raise SCons.Errors.UserError("While building `%s': "
896 "Cannot deduce file extension from source files: %s"
897 % (repr(list(map(str, target))), repr(list(map(str, source)))))
898 return act(target, source, env, exitstatfunc, presub,
899 show, execute, chdir, executor)
900
901 - def get_presig(self, target, source, env, executor=None):
902 """Return the signature contents of this action's command line.
903
904 This strips $(-$) and everything in between the string,
905 since those parts don't affect signatures.
906 """
907 return self._generate(target, source, env, 1, executor).get_presig(target, source, env)
908
911
912 - def get_varlist(self, target, source, env, executor=None):
914
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937 -class LazyAction(CommandGeneratorAction, CommandAction):
938
944
950
952 if env:
953 c = env.get(self.var, '')
954 else:
955 c = ''
956 gen_cmd = Action(c, **self.gen_kw)
957 if not gen_cmd:
958 raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c)))
959 return gen_cmd
960
961 - def _generate(self, target, source, env, for_signature, executor=None):
963
964 - def __call__(self, target, source, env, *args, **kw):
967
971
972 - def get_varlist(self, target, source, env, executor=None):
975
976
978 """Class for Python function actions."""
979
995
997 try:
998 return self.execfunction.__name__
999 except AttributeError:
1000 try:
1001 return self.execfunction.__class__.__name__
1002 except AttributeError:
1003 return "unknown_python_function"
1004
1005 - def strfunction(self, target, source, env, executor=None):
1025 return '[' + ", ".join(map(quote, a)) + ']'
1026 try:
1027 strfunc = self.execfunction.strfunction
1028 except AttributeError:
1029 pass
1030 else:
1031 if strfunc is None:
1032 return None
1033 if callable(strfunc):
1034 return strfunc(target, source, env)
1035 name = self.function_name()
1036 tstr = array(target)
1037 sstr = array(source)
1038 return "%s(%s, %s)" % (name, tstr, sstr)
1039
1041 name = self.function_name()
1042 if name == 'ActionCaller':
1043 return str(self.execfunction)
1044 return "%s(target, source, env)" % name
1045
1046 - def execute(self, target, source, env, executor=None):
1047 exc_info = (None,None,None)
1048 try:
1049 if executor:
1050 target = executor.get_all_targets()
1051 source = executor.get_all_sources()
1052 rsources = list(map(rfile, source))
1053 try:
1054 result = self.execfunction(target=target, source=rsources, env=env)
1055 except KeyboardInterrupt, e:
1056 raise
1057 except SystemExit, e:
1058 raise
1059 except Exception, e:
1060 result = e
1061 exc_info = sys.exc_info()
1062
1063 if result:
1064 result = SCons.Errors.convert_to_BuildError(result, exc_info)
1065 result.node=target
1066 result.action=self
1067 try:
1068 result.command=self.strfunction(target, source, env, executor)
1069 except TypeError:
1070 result.command=self.strfunction(target, source, env)
1071
1072
1073
1074
1075
1076
1077
1078 if (exc_info[1] and
1079 not isinstance(exc_info[1],EnvironmentError)):
1080 raise result
1081
1082 return result
1083 finally:
1084
1085
1086
1087 del exc_info
1088
1089
1091 """Return the signature contents of this callable action."""
1092 try:
1093 return self.gc(target, source, env)
1094 except AttributeError:
1095 return self.funccontents
1096
1099
1101 """Class for lists of other actions."""
1108 self.list = list(map(list_of_actions, actionlist))
1109
1110
1111 self.varlist = ()
1112 self.targets = '$TARGETS'
1113
1115 return '\n'.join([a.genstring(target, source, env) for a in self.list])
1116
1118 return '\n'.join(map(str, self.list))
1119
1123
1125 """Return the signature contents of this action list.
1126
1127 Simple concatenation of the signatures of the elements.
1128 """
1129 return "".join([x.get_contents(target, source, env) for x in self.list])
1130
1142
1148
1149 - def get_varlist(self, target, source, env, executor=None):
1155
1157 """A class for delaying calling an Action function with specific
1158 (positional and keyword) arguments until the Action is actually
1159 executed.
1160
1161 This class looks to the rest of the world like a normal Action object,
1162 but what it's really doing is hanging on to the arguments until we
1163 have a target, source and env to use for the expansion.
1164 """
1166 self.parent = parent
1167 self.args = args
1168 self.kw = kw
1169
1170 - def get_contents(self, target, source, env):
1171 actfunc = self.parent.actfunc
1172 try:
1173
1174 contents = str(actfunc.func_code.co_code)
1175 except AttributeError:
1176
1177 try:
1178 contents = str(actfunc.__call__.im_func.func_code.co_code)
1179 except AttributeError:
1180
1181
1182 contents = str(actfunc)
1183 contents = remove_set_lineno_codes(contents)
1184 return contents
1185
1186 - def subst(self, s, target, source, env):
1187
1188
1189 if is_List(s):
1190 result = []
1191 for elem in s:
1192 result.append(self.subst(elem, target, source, env))
1193 return self.parent.convert(result)
1194
1195
1196
1197
1198 if s == '$__env__':
1199 return env
1200 elif is_String(s):
1201 return env.subst(s, 1, target, source)
1202 return self.parent.convert(s)
1203
1205 return [self.subst(x, target, source, env) for x in self.args]
1206
1207 - def subst_kw(self, target, source, env):
1208 kw = {}
1209 for key in self.kw.keys():
1210 kw[key] = self.subst(self.kw[key], target, source, env)
1211 return kw
1212
1213 - def __call__(self, target, source, env, executor=None):
1214 args = self.subst_args(target, source, env)
1215 kw = self.subst_kw(target, source, env)
1216 return self.parent.actfunc(*args, **kw)
1217
1219 args = self.subst_args(target, source, env)
1220 kw = self.subst_kw(target, source, env)
1221 return self.parent.strfunc(*args, **kw)
1222
1224 return self.parent.strfunc(*self.args, **self.kw)
1225
1227 """A factory class that will wrap up an arbitrary function
1228 as an SCons-executable Action object.
1229
1230 The real heavy lifting here is done by the ActionCaller class.
1231 We just collect the (positional and keyword) arguments that we're
1232 called with and give them to the ActionCaller object we create,
1233 so it can hang onto them until it needs them.
1234 """
1235 - def __init__(self, actfunc, strfunc, convert=lambda x: x):
1236 self.actfunc = actfunc
1237 self.strfunc = strfunc
1238 self.convert = convert
1239
1244
1245
1246
1247
1248
1249
1250