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