83e4208246dc5eed6dd06fa6c02ea3b0357bc871
[scons.git] / src / engine / SCons / Action.py
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         This is what the Sig/*.py subsystem uses to decide if a target
35         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     strfunction()
61         Returns a substituted string representation of the Action.
62         This is used by the _ActionAction.show() command to display the
63         command/function that will be executed to generate the target(s).
64
65 There is a related independent ActionCaller class that looks like a
66 regular Action, and which serves as a wrapper for arbitrary functions
67 that we want to let the user specify the arguments to now, but actually
68 execute later (when an out-of-date check determines that it's needed to
69 be executed, for example).  Objects of this class are returned by an
70 ActionFactory class that provides a __call__() method as a convenient
71 way for wrapping up the functions.
72
73 """
74
75 #
76 # __COPYRIGHT__
77 #
78 # Permission is hereby granted, free of charge, to any person obtaining
79 # a copy of this software and associated documentation files (the
80 # "Software"), to deal in the Software without restriction, including
81 # without limitation the rights to use, copy, modify, merge, publish,
82 # distribute, sublicense, and/or sell copies of the Software, and to
83 # permit persons to whom the Software is furnished to do so, subject to
84 # the following conditions:
85 #
86 # The above copyright notice and this permission notice shall be included
87 # in all copies or substantial portions of the Software.
88 #
89 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
90 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
91 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
92 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
93 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
94 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
95 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
96 #
97
98 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
99
100 import os
101 import os.path
102 import re
103 import string
104 import sys
105
106 from SCons.Debug import logInstanceCreation
107 import SCons.Errors
108 import SCons.Util
109
110 class _Null:
111     pass
112
113 _null = _Null
114
115 print_actions = 1
116 execute_actions = 1
117 print_actions_presub = 0
118
119 default_ENV = None
120
121 def rfile(n):
122     try:
123         return n.rfile()
124     except AttributeError:
125         return n
126
127 def _actionAppend(act1, act2):
128     # This function knows how to slap two actions together.
129     # Mainly, it handles ListActions by concatenating into
130     # a single ListAction.
131     a1 = Action(act1)
132     a2 = Action(act2)
133     if a1 is None or a2 is None:
134         raise TypeError, "Cannot append %s to %s" % (type(act1), type(act2))
135     if isinstance(a1, ListAction):
136         if isinstance(a2, ListAction):
137             return ListAction(a1.list + a2.list)
138         else:
139             return ListAction(a1.list + [ a2 ])
140     else:
141         if isinstance(a2, ListAction):
142             return ListAction([ a1 ] + a2.list)
143         else:
144             return ListAction([ a1, a2 ])
145
146 class CommandGenerator:
147     """
148     Wraps a command generator function so the Action() factory
149     function can tell a generator function from a function action.
150     """
151     def __init__(self, generator):
152         self.generator = generator
153
154     def __add__(self, other):
155         return _actionAppend(self, other)
156
157     def __radd__(self, other):
158         return _actionAppend(other, self)
159
160 def _do_create_action(act, *args, **kw):
161     """This is the actual "implementation" for the
162     Action factory method, below.  This handles the
163     fact that passing lists to Action() itself has
164     different semantics than passing lists as elements
165     of lists.
166
167     The former will create a ListAction, the latter
168     will create a CommandAction by converting the inner
169     list elements to strings."""
170
171     if isinstance(act, ActionBase):
172         return act
173     if SCons.Util.is_List(act):
174         return apply(CommandAction, (act,)+args, kw)
175     if isinstance(act, CommandGenerator):
176         return apply(CommandGeneratorAction, (act.generator,)+args, kw)
177     if callable(act):
178         return apply(FunctionAction, (act,)+args, kw)
179     if SCons.Util.is_String(act):
180         var=SCons.Util.get_environment_var(act)
181         if var:
182             # This looks like a string that is purely an Environment
183             # variable reference, like "$FOO" or "${FOO}".  We do
184             # something special here...we lazily evaluate the contents
185             # of that Environment variable, so a user could put something
186             # like a function or a CommandGenerator in that variable
187             # instead of a string.
188             return apply(LazyAction, (var,)+args, kw)
189         commands = string.split(str(act), '\n')
190         if len(commands) == 1:
191             return apply(CommandAction, (commands[0],)+args, kw)
192         else:
193             listCmdActions = map(lambda x, args=args, kw=kw:
194                                  apply(CommandAction, (x,)+args, kw),
195                                  commands)
196             return ListAction(listCmdActions)
197     return None
198
199 def Action(act, *args, **kw):
200     """A factory for action objects."""
201     if SCons.Util.is_List(act):
202         acts = map(lambda a, args=args, kw=kw:
203                           apply(_do_create_action, (a,)+args, kw),
204                    act)
205         acts = filter(None, acts)
206         if len(acts) == 1:
207             return acts[0]
208         else:
209             return ListAction(acts)
210     else:
211         return apply(_do_create_action, (act,)+args, kw)
212
213 class ActionBase:
214     """Base class for all types of action objects that can be held by
215     other objects (Builders, Executors, etc.)  This provides the
216     common methods for manipulating and combining those actions."""
217     
218     def __cmp__(self, other):
219         return cmp(self.__dict__, other)
220
221     def genstring(self, target, source, env):
222         return str(self)
223
224     def __add__(self, other):
225         return _actionAppend(self, other)
226
227     def __radd__(self, other):
228         return _actionAppend(other, self)
229
230     def presub_lines(self, env):
231         # CommandGeneratorAction needs a real environment
232         # in order to return the proper string here, since
233         # it may call LazyCmdGenerator, which looks up a key
234         # in that env.  So we temporarily remember the env here,
235         # and CommandGeneratorAction will use this env
236         # when it calls its _generate method.
237         self.presub_env = env
238         lines = string.split(str(self), '\n')
239         self.presub_env = None      # don't need this any more
240         return lines
241
242
243 class _ActionAction(ActionBase):
244     """Base class for actions that create output objects."""
245     def __init__(self, strfunction=_null, presub=_null, chdir=None, **kw):
246         if not strfunction is _null:
247             self.strfunction = strfunction
248         if presub is _null:
249             presub = print_actions_presub
250         self.presub = presub
251         self.chdir = chdir
252
253     def print_cmd_line(self, s, target, source, env):
254         sys.stdout.write(s + "\n")
255
256     def __call__(self, target, source, env,
257                                errfunc=None,
258                                presub=_null,
259                                show=_null,
260                                execute=_null,
261                                chdir=_null):
262         if not SCons.Util.is_List(target):
263             target = [target]
264         if not SCons.Util.is_List(source):
265             source = [source]
266         if presub is _null:  presub = self.presub
267         if show is _null:  show = print_actions
268         if execute is _null:  execute = execute_actions
269         if chdir is _null: chdir = self.chdir
270         save_cwd = None
271         if chdir:
272             save_cwd = os.getcwd()
273             try:
274                 chdir = str(chdir.abspath)
275             except AttributeError:
276                 if not SCons.Util.is_String(chdir):
277                     chdir = str(target[0].dir)
278         if presub:
279             t = string.join(map(str, target), ' and ')
280             l = string.join(self.presub_lines(env), '\n  ')
281             out = "Building %s with action:\n  %s\n" % (t, l)
282             sys.stdout.write(out)
283         s = None
284         if show and self.strfunction:
285             s = self.strfunction(target, source, env)
286             if s:
287                 if chdir:
288                     s = ('os.chdir(%s)\n' % repr(chdir)) + s
289                 try:
290                     get = env.get
291                 except AttributeError:
292                     print_func = self.print_cmd_line
293                 else:
294                     print_func = get('PRINT_CMD_LINE_FUNC')
295                     if not print_func:
296                         print_func = self.print_cmd_line
297                 print_func(s, target, source, env)
298         stat = 0
299         if execute:
300             if chdir:
301                 os.chdir(chdir)
302             try:
303                 stat = self.execute(target, source, env)
304                 if stat and errfunc:
305                     errfunc(stat)
306             finally:
307                 if save_cwd:
308                     os.chdir(save_cwd)
309         if s and save_cwd:
310             print_func('os.chdir(%s)' % repr(save_cwd), target, source, env)
311         return stat
312
313
314 def _string_from_cmd_list(cmd_list):
315     """Takes a list of command line arguments and returns a pretty
316     representation for printing."""
317     cl = []
318     for arg in map(str, cmd_list):
319         if ' ' in arg or '\t' in arg:
320             arg = '"' + arg + '"'
321         cl.append(arg)
322     return string.join(cl)
323
324 class CommandAction(_ActionAction):
325     """Class for command-execution actions."""
326     def __init__(self, cmd, cmdstr=None, *args, **kw):
327         # Cmd can actually be a list or a single item; if it's a
328         # single item it should be the command string to execute; if a
329         # list then it should be the words of the command string to
330         # execute.  Only a single command should be executed by this
331         # object; lists of commands should be handled by embedding
332         # these objects in a ListAction object (which the Action()
333         # factory above does).  cmd will be passed to
334         # Environment.subst_list() for substituting environment
335         # variables.
336         if __debug__: logInstanceCreation(self)
337         apply(_ActionAction.__init__, (self,)+args, kw)
338         if SCons.Util.is_List(cmd):
339             if filter(SCons.Util.is_List, cmd):
340                 raise TypeError, "CommandAction should be given only " \
341                       "a single command"
342         self.cmd_list = cmd
343         self.cmdstr = cmdstr
344
345     def __str__(self):
346         if SCons.Util.is_List(self.cmd_list):
347             return string.join(map(str, self.cmd_list), ' ')
348         return str(self.cmd_list)
349
350     def strfunction(self, target, source, env):
351         if not self.cmdstr is None:
352             c = env.subst(self.cmdstr, 0, target, source)
353             if c:
354                 return c
355         cmd_list = env.subst_list(self.cmd_list, 0, target, source)
356         return _string_from_cmd_list(cmd_list[0])
357
358     def execute(self, target, source, env):
359         """Execute a command action.
360
361         This will handle lists of commands as well as individual commands,
362         because construction variable substitution may turn a single
363         "command" into a list.  This means that this class can actually
364         handle lists of commands, even though that's not how we use it
365         externally.
366         """
367         from SCons.Util import is_String, is_List, flatten, escape_list
368
369         try:
370             shell = env['SHELL']
371         except KeyError:
372             raise SCons.Errors.UserError('Missing SHELL construction variable.')
373
374         try:
375             spawn = env['SPAWN']
376         except KeyError:
377             raise SCons.Errors.UserError('Missing SPAWN construction variable.')
378
379         escape = env.get('ESCAPE', lambda x: x)
380
381         try:
382             ENV = env['ENV']
383         except KeyError:
384             global default_ENV
385             if not default_ENV:
386                 import SCons.Environment
387                 default_ENV = SCons.Environment.Environment()['ENV']
388             ENV = default_ENV
389
390         # Ensure that the ENV values are all strings:
391         for key, value in ENV.items():
392             if not is_String(value):
393                 if is_List(value):
394                     # If the value is a list, then we assume it is a
395                     # path list, because that's a pretty common list-like
396                     # value to stick in an environment variable:
397                     value = flatten(value)
398                     ENV[key] = string.join(map(str, value), os.pathsep)
399                 else:
400                     # If it isn't a string or a list, then we just coerce
401                     # it to a string, which is the proper way to handle
402                     # Dir and File instances and will produce something
403                     # reasonable for just about everything else:
404                     ENV[key] = str(value)
405
406         cmd_list = env.subst_list(self.cmd_list, 0, target,
407                                   map(rfile, source))
408
409         # Use len() to filter out any "command" that's zero-length.
410         for cmd_line in filter(len, cmd_list):
411             # Escape the command line for the interpreter we are using.
412             cmd_line = escape_list(cmd_line, escape)
413             result = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
414             if result:
415                 return result
416         return 0
417
418     def get_contents(self, target, source, env):
419         """Return the signature contents of this action's command line.
420
421         This strips $(-$) and everything in between the string,
422         since those parts don't affect signatures.
423         """
424         cmd = self.cmd_list
425         if SCons.Util.is_List(cmd):
426             cmd = string.join(map(str, cmd))
427         else:
428             cmd = str(cmd)
429         return env.subst_target_source(cmd, SCons.Util.SUBST_SIG, target, source)
430
431 class CommandGeneratorAction(ActionBase):
432     """Class for command-generator actions."""
433     def __init__(self, generator, *args, **kw):
434         if __debug__: logInstanceCreation(self)
435         self.generator = generator
436         self.gen_kw = kw
437
438     def _generate(self, target, source, env, for_signature):
439         # ensure that target is a list, to make it easier to write
440         # generator functions:
441         if not SCons.Util.is_List(target):
442             target = [target]
443
444         ret = self.generator(target=target, source=source, env=env, for_signature=for_signature)
445         gen_cmd = apply(Action, (ret,), self.gen_kw)
446         if not gen_cmd:
447             raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
448         return gen_cmd
449
450     def __str__(self):
451         try:
452             env = self.presub_env or {}
453         except AttributeError:
454             env = {}
455         act = self._generate([], [], env, 1)
456         return str(act)
457
458     def genstring(self, target, source, env):
459         return self._generate(target, source, env, 1).genstring(target, source, env)
460
461     def __call__(self, target, source, env, errfunc=None, presub=_null,
462                  show=_null, execute=_null, chdir=_null):
463         act = self._generate(target, source, env, 0)
464         return act(target, source, env, errfunc, presub,
465                    show, execute, chdir)
466
467     def get_contents(self, target, source, env):
468         """Return the signature contents of this action's command line.
469
470         This strips $(-$) and everything in between the string,
471         since those parts don't affect signatures.
472         """
473         return self._generate(target, source, env, 1).get_contents(target, source, env)
474
475 # Ooh, polymorphism -- pretty scary, eh, kids?
476 #
477 # A LazyCmdAction is a kind of hybrid generator and command action for
478 # strings of the form "$VAR".  These strings normally expand to other
479 # strings (think "$CCCOM" to "$CC -c -o $TARGET $SOURCE"), but we also
480 # want to be able to replace them with functions in the construction
481 # environment.  Consequently, we want lazy evaluation and creation of
482 # an Action in the case of the function, but that's overkill in the more
483 # normal case of expansion to other strings.
484 #
485 # So we do this with a subclass that's both a generator *and*
486 # a command action.  The overridden methods all do a quick check
487 # of the construction variable, and if it's a string we just call
488 # the corresponding CommandAction method to do the heavy lifting.
489 # If not, then we call the same-named CommandGeneratorAction method.
490 # The CommandGeneratorAction methods work by using the overridden
491 # _generate() method, uses our own way of handling "generation" of an
492 # action based on what's in the construction variable.
493
494 class LazyAction(CommandGeneratorAction, CommandAction):
495     def __init__(self, var, *args, **kw):
496         if __debug__: logInstanceCreation(self)
497         apply(CommandAction.__init__, (self, '$'+var)+args, kw)
498         self.var = SCons.Util.to_String(var)
499         self.gen_kw = kw
500
501     def get_parent_class(self, env):
502         c = env.get(self.var)
503         if SCons.Util.is_String(c) and not '\n' in c:
504             return CommandAction
505         return CommandGeneratorAction
506
507     def _generate(self, target, source, env, for_signature):
508         c = env.get(self.var, '')
509         gen_cmd = apply(Action, (c,), self.gen_kw)
510         if not gen_cmd:
511             raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c)))
512         return gen_cmd
513
514     def __call__(self, target, source, env, *args, **kw):
515         args = (self, target, source, env) + args
516         c = self.get_parent_class(env)
517         return apply(c.__call__, args, kw)
518
519     def get_contents(self, target, source, env):
520         c = self.get_parent_class(env)
521         return c.get_contents(self, target, source, env)
522
523 class FunctionAction(_ActionAction):
524     """Class for Python function actions."""
525
526     def __init__(self, execfunction, *args, **kw):
527         if __debug__: logInstanceCreation(self)
528         self.execfunction = execfunction
529         apply(_ActionAction.__init__, (self,)+args, kw)
530         self.varlist = kw.get('varlist', [])
531
532     def function_name(self):
533         try:
534             return self.execfunction.__name__
535         except AttributeError:
536             try:
537                 return self.execfunction.__class__.__name__
538             except AttributeError:
539                 return "unknown_python_function"
540
541     def strfunction(self, target, source, env):
542         def array(a):
543             def quote(s):
544                 return '"' + str(s) + '"'
545             return '[' + string.join(map(quote, a), ", ") + ']'
546         try:
547             strfunc = self.execfunction.strfunction
548         except AttributeError:
549             pass
550         else:
551             if strfunc is None:
552                 return None
553             if callable(strfunc):
554                 return strfunc(target, source, env)
555         name = self.function_name()
556         tstr = array(target)
557         sstr = array(source)
558         return "%s(%s, %s)" % (name, tstr, sstr)
559
560     def __str__(self):
561         return "%s(target, source, env)" % self.function_name()
562
563     def execute(self, target, source, env):
564         rsources = map(rfile, source)
565         try:
566             result = self.execfunction(target=target, source=rsources, env=env)
567         except EnvironmentError, e:
568             # If an IOError/OSError happens, raise a BuildError.
569             raise SCons.Errors.BuildError(node=target, errstr=e.strerror)
570         return result
571
572     def get_contents(self, target, source, env):
573         """Return the signature contents of this callable action.
574
575         By providing direct access to the code object of the
576         function, Python makes this extremely easy.  Hooray!
577         """
578         try:
579             # "self.execfunction" is a function.
580             contents = str(self.execfunction.func_code.co_code)
581         except AttributeError:
582             # "self.execfunction" is a callable object.
583             try:
584                 contents = str(self.execfunction.__call__.im_func.func_code.co_code)
585             except AttributeError:
586                 try:
587                     # See if execfunction will do the heavy lifting for us.
588                     gc = self.execfunction.get_contents
589                 except AttributeError:
590                     # This is weird, just do the best we can.
591                     contents = str(self.execfunction)
592                 else:
593                     contents = gc(target, source, env)
594         return contents + env.subst(string.join(map(lambda v: '${'+v+'}',
595                                                      self.varlist)))
596
597 class ListAction(ActionBase):
598     """Class for lists of other actions."""
599     def __init__(self, list):
600         if __debug__: logInstanceCreation(self)
601         def list_of_actions(x):
602             if isinstance(x, ActionBase):
603                 return x
604             return Action(x)
605         self.list = map(list_of_actions, list)
606
607     def genstring(self, target, source, env):
608         return string.join(map(lambda a, t=target, s=source, e=env:
609                                   a.genstring(t, s, e),
610                                self.list),
611                            '\n')
612
613     def __str__(self):
614         return string.join(map(str, self.list), '\n')
615     
616     def presub_lines(self, env):
617         return SCons.Util.flatten(map(lambda a, env=env:
618                                       a.presub_lines(env),
619                                       self.list))
620
621     def get_contents(self, target, source, env):
622         """Return the signature contents of this action list.
623
624         Simple concatenation of the signatures of the elements.
625         """
626         return string.join(map(lambda x, t=target, s=source, e=env:
627                                       x.get_contents(t, s, e),
628                                self.list),
629                            "")
630
631     def __call__(self, target, source, env, errfunc=None, presub=_null,
632                  show=_null, execute=_null, chdir=_null):
633         for act in self.list:
634             stat = act(target, source, env, errfunc, presub,
635                        show, execute, chdir)
636             if stat:
637                 return stat
638         return 0
639
640 class ActionCaller:
641     """A class for delaying calling an Action function with specific
642     (positional and keyword) arguments until the Action is actually
643     executed.
644
645     This class looks to the rest of the world like a normal Action object,
646     but what it's really doing is hanging on to the arguments until we
647     have a target, source and env to use for the expansion.
648     """
649     def __init__(self, parent, args, kw):
650         self.parent = parent
651         self.args = args
652         self.kw = kw
653     def get_contents(self, target, source, env):
654         actfunc = self.parent.actfunc
655         try:
656             # "self.actfunc" is a function.
657             contents = str(actfunc.func_code.co_code)
658         except AttributeError:
659             # "self.actfunc" is a callable object.
660             try:
661                 contents = str(actfunc.__call__.im_func.func_code.co_code)
662             except AttributeError:
663                 # No __call__() method, so it might be a builtin
664                 # or something like that.  Do the best we can.
665                 contents = str(actfunc)
666         return contents
667     def subst(self, s, target, source, env):
668         # Special-case hack:  Let a custom function wrapped in an
669         # ActionCaller get at the environment through which the action
670         # was called by using this hard-coded value as a special return.
671         if s == '$__env__':
672             return env
673         else:
674             return env.subst(s, 0, target, source)
675     def subst_args(self, target, source, env):
676         return map(lambda x, self=self, t=target, s=source, e=env:
677                           self.subst(x, t, s, e),
678                    self.args)
679     def subst_kw(self, target, source, env):
680         kw = {}
681         for key in self.kw.keys():
682             kw[key] = self.subst(self.kw[key], target, source, env)
683         return kw
684     def __call__(self, target, source, env):
685         args = self.subst_args(target, source, env)
686         kw = self.subst_kw(target, source, env)
687         return apply(self.parent.actfunc, args, kw)
688     def strfunction(self, target, source, env):
689         args = self.subst_args(target, source, env)
690         kw = self.subst_kw(target, source, env)
691         return apply(self.parent.strfunc, args, kw)
692
693 class ActionFactory:
694     """A factory class that will wrap up an arbitrary function
695     as an SCons-executable Action object.
696
697     The real heavy lifting here is done by the ActionCaller class.
698     We just collect the (positional and keyword) arguments that we're
699     called with and give them to the ActionCaller object we create,
700     so it can hang onto them until it needs them.
701     """
702     def __init__(self, actfunc, strfunc):
703         self.actfunc = actfunc
704         self.strfunc = strfunc
705     def __call__(self, *args, **kw):
706         ac = ActionCaller(self, args, kw)
707         action = Action(ac, strfunction=ac.strfunction)
708         # action will be a FunctionAction; if left to its own devices,
709         # a genstr or str of this action will just show
710         # "ActionCaller(target, source, env)".  Override that with the
711         # description from strfunc.  Note that the apply is evaluated
712         # right now; __str__ is set to a (lambda) function that just
713         # returns the stored result of the evaluation whenever called.
714         action.__str__ = lambda name=apply(self.strfunc, args, kw): name
715         return action