~ubuntu-branches/ubuntu/trusty/blender/trusty-proposed

« back to all changes in this revision

Viewing changes to scons/scons-local/SCons/Action.py

  • Committer: Package Import Robot
  • Author(s): Matteo F. Vescovi
  • Date: 2013-08-14 10:43:49 UTC
  • mfrom: (14.2.19 sid)
  • Revision ID: package-import@ubuntu.com-20130814104349-t1d5mtwkphp12dyj
Tags: 2.68a-3
* Upload to unstable
* debian/: python3.3 Depends simplified
  - debian/control: python3.3 Depends dropped
    for blender-data package
  - 0001-blender_thumbnailer.patch refreshed
* debian/control: libavcodec b-dep versioning dropped

Show diffs side-by-side

added added

removed removed

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