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 TODO(?): Change this to always return ascii/bytes and not unicode (or py3 strings)
64
65 strfunction()
66 Returns a substituted string representation of the Action.
67 This is used by the _ActionAction.show() command to display the
68 command/function that will be executed to generate the target(s).
69
70 There is a related independent ActionCaller class that looks like a
71 regular Action, and which serves as a wrapper for arbitrary functions
72 that we want to let the user specify the arguments to now, but actually
73 execute later (when an out-of-date check determines that it's needed to
74 be executed, for example). Objects of this class are returned by an
75 ActionFactory class that provides a __call__() method as a convenient
76 way for wrapping up the functions.
77
78 """
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101 __revision__ = "src/engine/SCons/Action.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
102
103 import os
104 import pickle
105 import re
106 import sys
107 import subprocess
108 import itertools
109 import inspect
110
111 import SCons.Debug
112 from SCons.Debug import logInstanceCreation
113 import SCons.Errors
114 import SCons.Util
115 import SCons.Subst
116
117
118 is_String = SCons.Util.is_String
119 is_List = SCons.Util.is_List
120
123
124 print_actions = 1
125 execute_actions = 1
126 print_actions_presub = 0
127
128
129
130
131
132
133
134 ACTION_SIGNATURE_PICKLE_PROTOCOL = 1
135
136
138 try:
139 return n.rfile()
140 except AttributeError:
141 return n
142
143
146
147 strip_quotes = re.compile('^[\'"](.*)[\'"]$')
148
149
151 """Return the signature contents of a callable Python object.
152 """
153 try:
154
155 return _function_contents(obj.__func__)
156
157 except AttributeError:
158 try:
159
160 return _function_contents(obj.__call__.__func__)
161
162 except AttributeError:
163 try:
164
165 return _code_contents(obj)
166
167 except AttributeError:
168
169 return _function_contents(obj)
170
171
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
180 return _function_contents(obj.__func__)
181
182 except AttributeError:
183 try:
184
185 return _function_contents(obj.__call__.__func__)
186
187 except AttributeError:
188 try:
189
190 return _code_contents(obj)
191
192 except AttributeError:
193 try:
194
195 return _function_contents(obj)
196
197 except AttributeError as ae:
198
199 try:
200 return _object_instance_content(obj)
201
202
203
204 except (pickle.PicklingError, TypeError, AttributeError) as ex:
205
206
207
208
209
210 return bytearray(repr(obj), 'utf-8')
211
212
213 -def _code_contents(code, docstring=None):
214 """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
247
248
249
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
258
259
260
261
262 z = [_object_contents(cc) for cc in code.co_consts[1:]]
263 contents.extend(b',(')
264 contents.extend(bytearray(',', 'utf-8').join(z))
265 contents.extend(b')')
266
267
268
269
270
271 z= [bytearray(_object_contents(cc)) for cc in code.co_names]
272 contents.extend(b',(')
273 contents.extend(bytearray(',','utf-8').join(z))
274 contents.extend(b')')
275
276
277 contents.extend(b',(')
278 contents.extend(code.co_code)
279 contents.extend(b')')
280
281 return contents
282
283
285 """
286 The signature is as follows (should be byte/chars):
287 < _code_contents (see above) from func.__code__ >
288 ,( comma separated _object_contents for function argument defaults)
289 ,( comma separated _object_contents for any closure contents )
290
291
292 See also: https://docs.python.org/3/reference/datamodel.html
293 - func.__code__ - The code object representing the compiled function body.
294 - func.__defaults__ - A tuple containing default argument values for those arguments that have defaults, or None if no arguments have a default value
295 - func.__closure__ - None or a tuple of cells that contain bindings for the function's free variables.
296
297 :Returns:
298 Signature contents of a function. (in bytes)
299 """
300
301 contents = [_code_contents(func.__code__, func.__doc__)]
302
303
304 if func.__defaults__:
305
306 function_defaults_contents = [_object_contents(cc) for cc in func.__defaults__]
307
308 defaults = bytearray(b',(')
309 defaults.extend(bytearray(b',').join(function_defaults_contents))
310 defaults.extend(b')')
311
312 contents.append(defaults)
313 else:
314 contents.append(b',()')
315
316
317 closure = func.__closure__ or []
318
319 try:
320 closure_contents = [_object_contents(x.cell_contents) for x in closure]
321 except AttributeError:
322 closure_contents = []
323
324 contents.append(b',(')
325 contents.append(bytearray(b',').join(closure_contents))
326 contents.append(b')')
327
328 retval = bytearray(b'').join(contents)
329 return retval
330
331
333 """
334 Returns consistant content for a action class or an instance thereof
335
336 :Parameters:
337 - `obj` Should be either and action class or an instance thereof
338
339 :Returns:
340 bytearray or bytes representing the obj suitable for generating a signature from.
341 """
342 retval = bytearray()
343
344 if obj is None:
345 return b'N.'
346
347 if isinstance(obj, SCons.Util.BaseStringTypes):
348 return SCons.Util.to_bytes(obj)
349
350 inst_class = obj.__class__
351 inst_class_name = bytearray(obj.__class__.__name__,'utf-8')
352 inst_class_module = bytearray(obj.__class__.__module__,'utf-8')
353 inst_class_hierarchy = bytearray(repr(inspect.getclasstree([obj.__class__,])),'utf-8')
354
355
356 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))) ]
357 properties.sort()
358 properties_str = ','.join(["%s=%s"%(p[0],p[1]) for p in properties])
359 properties_bytes = bytearray(properties_str,'utf-8')
360
361 methods = [p for p in dir(obj) if inspect.ismethod(getattr(obj, p))]
362 methods.sort()
363
364 method_contents = []
365 for m in methods:
366
367 v = _function_contents(getattr(obj, m))
368
369 method_contents.append(v)
370
371 retval = bytearray(b'{')
372 retval.extend(inst_class_name)
373 retval.extend(b":")
374 retval.extend(inst_class_module)
375 retval.extend(b'}[[')
376 retval.extend(inst_class_hierarchy)
377 retval.extend(b']]{{')
378 retval.extend(bytearray(b",").join(method_contents))
379 retval.extend(b"}}{{{")
380 retval.extend(properties_bytes)
381 retval.extend(b'}}}')
382 return retval
383
384
385
386
387
388
389
390
392
393
394
395 a1 = Action(act1)
396 a2 = Action(act2)
397 if a1 is None:
398 return a2
399 if a2 is None:
400 return a1
401 if isinstance(a1, ListAction):
402 if isinstance(a2, ListAction):
403 return ListAction(a1.list + a2.list)
404 else:
405 return ListAction(a1.list + [ a2 ])
406 else:
407 if isinstance(a2, ListAction):
408 return ListAction([ a1 ] + a2.list)
409 else:
410 return ListAction([ a1, a2 ])
411
412
414 """This converts any arguments after the action argument into
415 their equivalent keywords and adds them to the kw argument.
416 """
417 v = kw.get('varlist', ())
418
419 if is_String(v): v = (v,)
420 kw['varlist'] = tuple(v)
421 if args:
422
423 cmdstrfunc = args[0]
424 if cmdstrfunc is None or is_String(cmdstrfunc):
425 kw['cmdstr'] = cmdstrfunc
426 elif callable(cmdstrfunc):
427 kw['strfunction'] = cmdstrfunc
428 else:
429 raise SCons.Errors.UserError(
430 'Invalid command display variable type. '
431 'You must either pass a string or a callback which '
432 'accepts (target, source, env) as parameters.')
433 if len(args) > 1:
434 kw['varlist'] = tuple(SCons.Util.flatten(args[1:])) + kw['varlist']
435 if kw.get('strfunction', _null) is not _null \
436 and kw.get('cmdstr', _null) is not _null:
437 raise SCons.Errors.UserError(
438 'Cannot have both strfunction and cmdstr args to Action()')
439
440
442 """This is the actual "implementation" for the
443 Action factory method, below. This handles the
444 fact that passing lists to Action() itself has
445 different semantics than passing lists as elements
446 of lists.
447
448 The former will create a ListAction, the latter
449 will create a CommandAction by converting the inner
450 list elements to strings."""
451
452 if isinstance(act, ActionBase):
453 return act
454
455 if is_String(act):
456 var=SCons.Util.get_environment_var(act)
457 if var:
458
459
460
461
462
463
464 return LazyAction(var, kw)
465 commands = str(act).split('\n')
466 if len(commands) == 1:
467 return CommandAction(commands[0], **kw)
468
469
470 return _do_create_list_action(commands, kw)
471
472 if is_List(act):
473 return CommandAction(act, **kw)
474
475 if callable(act):
476 try:
477 gen = kw['generator']
478 del kw['generator']
479 except KeyError:
480 gen = 0
481 if gen:
482 action_type = CommandGeneratorAction
483 else:
484 action_type = FunctionAction
485 return action_type(act, kw)
486
487
488 if isinstance(act, int) or isinstance(act, float):
489 raise TypeError("Don't know how to create an Action from a number (%s)"%act)
490
491 return None
492
493
495 """A factory for list actions. Convert the input list into Actions
496 and then wrap them in a ListAction."""
497 acts = []
498 for a in act:
499 aa = _do_create_action(a, kw)
500 if aa is not None: acts.append(aa)
501 if not acts:
502 return ListAction([])
503 elif len(acts) == 1:
504 return acts[0]
505 else:
506 return ListAction(acts)
507
508
516
517
519 """Base class for all types of action objects that can be held by
520 other objects (Builders, Executors, etc.) This provides the
521 common methods for manipulating and combining those actions."""
522
524 return self.__dict__ == other
525
528
529 batch_key = no_batch_key
530
533
534 - def get_contents(self, target, source, env):
535 result = self.get_presig(target, source, env)
536
537 if not isinstance(result,(bytes, bytearray)):
538 result = bytearray("",'utf-8').join([ SCons.Util.to_bytes(r) for r in result ])
539 else:
540
541
542
543 result = bytearray(result)
544
545
546
547
548
549
550 vl = self.get_varlist(target, source, env)
551 if is_String(vl): vl = (vl,)
552 for v in vl:
553
554 if isinstance(result, bytearray):
555 result.extend(SCons.Util.to_bytes(env.subst_target_source('${'+v+'}', SCons.Subst.SUBST_SIG, target, source)))
556 else:
557 raise Exception("WE SHOULD NEVER GET HERE result should be bytearray not:%s"%type(result))
558
559
560
561 if isinstance(result, (bytes,bytearray)):
562 return result
563 else:
564 raise Exception("WE SHOULD NEVER GET HERE - #2 result should be bytearray not:%s" % type(result))
565
566
569
572
574
575
576
577
578
579
580 self.presub_env = env
581 lines = str(self).split('\n')
582 self.presub_env = None
583 return lines
584
585 - def get_varlist(self, target, source, env, executor=None):
587
589 """
590 Returns the type of targets ($TARGETS, $CHANGED_TARGETS) used
591 by this action.
592 """
593 return self.targets
594
595
597 """Base class for actions that create output objects."""
598 - def __init__(self, cmdstr=_null, strfunction=_null, varlist=(),
599 presub=_null, chdir=None, exitstatfunc=None,
600 batch_key=None, targets='$TARGETS',
601 **kw):
625 batch_key = default_batch_key
626 SCons.Util.AddMethod(self, batch_key, 'batch_key')
627
629 """
630 In python 3, and in some of our tests, sys.stdout is
631 a String io object, and it takes unicode strings only
632 In other cases it's a regular Python 2.x file object
633 which takes strings (bytes), and if you pass those a
634 unicode object they try to decode with 'ascii' codec
635 which fails if the cmd line has any hi-bit-set chars.
636 This code assumes s is a regular string, but should
637 work if it's unicode too.
638 """
639 try:
640 sys.stdout.write(s + u"\n")
641 except UnicodeDecodeError:
642 sys.stdout.write(s + "\n")
643
651 if not is_List(target):
652 target = [target]
653 if not is_List(source):
654 source = [source]
655
656 if presub is _null:
657 presub = self.presub
658 if presub is _null:
659 presub = print_actions_presub
660 if exitstatfunc is _null: exitstatfunc = self.exitstatfunc
661 if show is _null: show = print_actions
662 if execute is _null: execute = execute_actions
663 if chdir is _null: chdir = self.chdir
664 save_cwd = None
665 if chdir:
666 save_cwd = os.getcwd()
667 try:
668 chdir = str(chdir.get_abspath())
669 except AttributeError:
670 if not is_String(chdir):
671 if executor:
672 chdir = str(executor.batches[0].targets[0].dir)
673 else:
674 chdir = str(target[0].dir)
675 if presub:
676 if executor:
677 target = executor.get_all_targets()
678 source = executor.get_all_sources()
679 t = ' and '.join(map(str, target))
680 l = '\n '.join(self.presub_lines(env))
681 out = u"Building %s with action:\n %s\n" % (t, l)
682 sys.stdout.write(out)
683 cmd = None
684 if show and self.strfunction:
685 if executor:
686 target = executor.get_all_targets()
687 source = executor.get_all_sources()
688 try:
689 cmd = self.strfunction(target, source, env, executor)
690 except TypeError:
691 cmd = self.strfunction(target, source, env)
692 if cmd:
693 if chdir:
694 cmd = ('os.chdir(%s)\n' % repr(chdir)) + cmd
695 try:
696 get = env.get
697 except AttributeError:
698 print_func = self.print_cmd_line
699 else:
700 print_func = get('PRINT_CMD_LINE_FUNC')
701 if not print_func:
702 print_func = self.print_cmd_line
703 print_func(cmd, target, source, env)
704 stat = 0
705 if execute:
706 if chdir:
707 os.chdir(chdir)
708 try:
709 stat = self.execute(target, source, env, executor=executor)
710 if isinstance(stat, SCons.Errors.BuildError):
711 s = exitstatfunc(stat.status)
712 if s:
713 stat.status = s
714 else:
715 stat = s
716 else:
717 stat = exitstatfunc(stat)
718 finally:
719 if save_cwd:
720 os.chdir(save_cwd)
721 if cmd and save_cwd:
722 print_func('os.chdir(%s)' % repr(save_cwd), target, source, env)
723
724 return stat
725
726
728 """Takes a list of command line arguments and returns a pretty
729 representation for printing."""
730 cl = []
731 for arg in map(str, cmd_list):
732 if ' ' in arg or '\t' in arg:
733 arg = '"' + arg + '"'
734 cl.append(arg)
735 return ' '.join(cl)
736
737 default_ENV = None
738
739
741 """
742 A fiddlin' little function that has an 'import SCons.Environment' which
743 can't be moved to the top level without creating an import loop. Since
744 this import creates a local variable named 'SCons', it blocks access to
745 the global variable, so we move it here to prevent complaints about local
746 variables being used uninitialized.
747 """
748 global default_ENV
749 try:
750 return env['ENV']
751 except KeyError:
752 if not default_ENV:
753 import SCons.Environment
754
755
756
757
758
759 default_ENV = SCons.Environment.Environment()['ENV']
760 return default_ENV
761
762
763 -def _subproc(scons_env, cmd, error = 'ignore', **kw):
764 """Do common setup for a subprocess.Popen() call
765
766 This function is still in draft mode. We're going to need something like
767 it in the long run as more and more places use subprocess, but I'm sure
768 it'll have to be tweaked to get the full desired functionality.
769 one special arg (so far?), 'error', to tell what to do with exceptions.
770 """
771
772 io = kw.get('stdin')
773 if is_String(io) and io == 'devnull':
774 kw['stdin'] = open(os.devnull)
775 io = kw.get('stdout')
776 if is_String(io) and io == 'devnull':
777 kw['stdout'] = open(os.devnull, 'w')
778 io = kw.get('stderr')
779 if is_String(io) and io == 'devnull':
780 kw['stderr'] = open(os.devnull, 'w')
781
782
783 ENV = kw.get('env', None)
784 if ENV is None: ENV = get_default_ENV(scons_env)
785
786
787 new_env = {}
788 for key, value in ENV.items():
789 if is_List(value):
790
791
792
793 value = SCons.Util.flatten_sequence(value)
794 new_env[key] = os.pathsep.join(map(str, value))
795 else:
796
797
798
799
800
801
802 new_env[key] = str(value)
803 kw['env'] = new_env
804
805 try:
806 return subprocess.Popen(cmd, **kw)
807 except EnvironmentError as e:
808 if error == 'raise': raise
809
810 class dummyPopen(object):
811 def __init__(self, e): self.exception = e
812 def communicate(self, input=None): return ('', '')
813 def wait(self): return -self.exception.errno
814 stdin = None
815 class f(object):
816 def read(self): return ''
817 def readline(self): return ''
818 def __iter__(self): return iter(())
819 stdout = stderr = f()
820 return dummyPopen(e)
821
822
824 """Class for command-execution actions."""
826
827
828
829
830
831
832
833
834
835 if SCons.Debug.track_instances: logInstanceCreation(self, 'Action.CommandAction')
836
837 _ActionAction.__init__(self, **kw)
838 if is_List(cmd):
839 if [c for c in cmd if is_List(c)]:
840 raise TypeError("CommandAction should be given only " \
841 "a single command")
842 self.cmd_list = cmd
843
845 if is_List(self.cmd_list):
846 return ' '.join(map(str, self.cmd_list))
847 return str(self.cmd_list)
848
849 - def process(self, target, source, env, executor=None):
869
870 - def strfunction(self, target, source, env, executor=None):
871 if self.cmdstr is None:
872 return None
873 if self.cmdstr is not _null:
874 from SCons.Subst import SUBST_RAW
875 if executor:
876 c = env.subst(self.cmdstr, SUBST_RAW, executor=executor)
877 else:
878 c = env.subst(self.cmdstr, SUBST_RAW, target, source)
879 if c:
880 return c
881 cmd_list, ignore, silent = self.process(target, source, env, executor)
882 if silent:
883 return ''
884 return _string_from_cmd_list(cmd_list[0])
885
886 - def execute(self, target, source, env, executor=None):
887 """Execute a command action.
888
889 This will handle lists of commands as well as individual commands,
890 because construction variable substitution may turn a single
891 "command" into a list. This means that this class can actually
892 handle lists of commands, even though that's not how we use it
893 externally.
894 """
895 escape_list = SCons.Subst.escape_list
896 flatten_sequence = SCons.Util.flatten_sequence
897
898 try:
899 shell = env['SHELL']
900 except KeyError:
901 raise SCons.Errors.UserError('Missing SHELL construction variable.')
902
903 try:
904 spawn = env['SPAWN']
905 except KeyError:
906 raise SCons.Errors.UserError('Missing SPAWN construction variable.')
907 else:
908 if is_String(spawn):
909 spawn = env.subst(spawn, raw=1, conv=lambda x: x)
910
911 escape = env.get('ESCAPE', lambda x: x)
912
913 ENV = get_default_ENV(env)
914
915
916 for key, value in ENV.items():
917 if not is_String(value):
918 if is_List(value):
919
920
921
922 value = flatten_sequence(value)
923 ENV[key] = os.pathsep.join(map(str, value))
924 else:
925
926
927
928
929 ENV[key] = str(value)
930
931 if executor:
932 target = executor.get_all_targets()
933 source = executor.get_all_sources()
934 cmd_list, ignore, silent = self.process(target, list(map(rfile, source)), env, executor)
935
936
937 for cmd_line in filter(len, cmd_list):
938
939 cmd_line = escape_list(cmd_line, escape)
940 result = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
941 if not ignore and result:
942 msg = "Error %s" % result
943 return SCons.Errors.BuildError(errstr=msg,
944 status=result,
945 action=self,
946 command=cmd_line)
947 return 0
948
949 - def get_presig(self, target, source, env, executor=None):
950 """Return the signature contents of this action's command line.
951
952 This strips $(-$) and everything in between the string,
953 since those parts don't affect signatures.
954 """
955 from SCons.Subst import SUBST_SIG
956 cmd = self.cmd_list
957 if is_List(cmd):
958 cmd = ' '.join(map(str, cmd))
959 else:
960 cmd = str(cmd)
961 if executor:
962 return env.subst_target_source(cmd, SUBST_SIG, executor=executor)
963 else:
964 return env.subst_target_source(cmd, SUBST_SIG, target, source)
965
988
989
991 """Class for command-generator actions."""
998
999 - def _generate(self, target, source, env, for_signature, executor=None):
1016
1018 try:
1019 env = self.presub_env
1020 except AttributeError:
1021 env = None
1022 if env is None:
1023 env = SCons.Defaults.DefaultEnvironment()
1024 act = self._generate([], [], env, 1)
1025 return str(act)
1026
1029
1030 - def genstring(self, target, source, env, executor=None):
1032
1035 act = self._generate(target, source, env, 0, executor)
1036 if act is None:
1037 raise SCons.Errors.UserError("While building `%s': "
1038 "Cannot deduce file extension from source files: %s"
1039 % (repr(list(map(str, target))), repr(list(map(str, source)))))
1040 return act(target, source, env, exitstatfunc, presub,
1041 show, execute, chdir, executor)
1042
1043 - def get_presig(self, target, source, env, executor=None):
1044 """Return the signature contents of this action's command line.
1045
1046 This strips $(-$) and everything in between the string,
1047 since those parts don't affect signatures.
1048 """
1049 return self._generate(target, source, env, 1, executor).get_presig(target, source, env)
1050
1053
1054 - def get_varlist(self, target, source, env, executor=None):
1056
1059
1060
1061 -class LazyAction(CommandGeneratorAction, CommandAction):
1062 """
1063 A LazyAction is a kind of hybrid generator and command action for
1064 strings of the form "$VAR". These strings normally expand to other
1065 strings (think "$CCCOM" to "$CC -c -o $TARGET $SOURCE"), but we also
1066 want to be able to replace them with functions in the construction
1067 environment. Consequently, we want lazy evaluation and creation of
1068 an Action in the case of the function, but that's overkill in the more
1069 normal case of expansion to other strings.
1070
1071 So we do this with a subclass that's both a generator *and*
1072 a command action. The overridden methods all do a quick check
1073 of the construction variable, and if it's a string we just call
1074 the corresponding CommandAction method to do the heavy lifting.
1075 If not, then we call the same-named CommandGeneratorAction method.
1076 The CommandGeneratorAction methods work by using the overridden
1077 _generate() method, that is, our own way of handling "generation" of
1078 an action based on what's in the construction variable.
1079 """
1080
1086
1092
1094 if env:
1095 c = env.get(self.var, '')
1096 else:
1097 c = ''
1098 gen_cmd = Action(c, **self.gen_kw)
1099 if not gen_cmd:
1100 raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c)))
1101 return gen_cmd
1102
1103 - def _generate(self, target, source, env, for_signature, executor=None):
1105
1106 - def __call__(self, target, source, env, *args, **kw):
1109
1113
1114 - def get_varlist(self, target, source, env, executor=None):
1117
1118
1120 """Class for Python function actions."""
1121
1137
1139 try:
1140 return self.execfunction.__name__
1141 except AttributeError:
1142 try:
1143 return self.execfunction.__class__.__name__
1144 except AttributeError:
1145 return "unknown_python_function"
1146
1147 - def strfunction(self, target, source, env, executor=None):
1168 return '[' + ", ".join(map(quote, a)) + ']'
1169 try:
1170 strfunc = self.execfunction.strfunction
1171 except AttributeError:
1172 pass
1173 else:
1174 if strfunc is None:
1175 return None
1176 if callable(strfunc):
1177 return strfunc(target, source, env)
1178 name = self.function_name()
1179 tstr = array(target)
1180 sstr = array(source)
1181 return "%s(%s, %s)" % (name, tstr, sstr)
1182
1184 name = self.function_name()
1185 if name == 'ActionCaller':
1186 return str(self.execfunction)
1187 return "%s(target, source, env)" % name
1188
1189 - def execute(self, target, source, env, executor=None):
1190 exc_info = (None,None,None)
1191 try:
1192 if executor:
1193 target = executor.get_all_targets()
1194 source = executor.get_all_sources()
1195 rsources = list(map(rfile, source))
1196 try:
1197 result = self.execfunction(target=target, source=rsources, env=env)
1198 except KeyboardInterrupt as e:
1199 raise
1200 except SystemExit as e:
1201 raise
1202 except Exception as e:
1203 result = e
1204 exc_info = sys.exc_info()
1205
1206 if result:
1207 result = SCons.Errors.convert_to_BuildError(result, exc_info)
1208 result.node=target
1209 result.action=self
1210 try:
1211 result.command=self.strfunction(target, source, env, executor)
1212 except TypeError:
1213 result.command=self.strfunction(target, source, env)
1214
1215
1216
1217
1218
1219
1220
1221 if (exc_info[1] and
1222 not isinstance(exc_info[1],EnvironmentError)):
1223 raise result
1224
1225 return result
1226 finally:
1227
1228
1229
1230 del exc_info
1231
1233 """Return the signature contents of this callable action."""
1234 try:
1235 return self.gc(target, source, env)
1236 except AttributeError:
1237 return self.funccontents
1238
1241
1243 """Class for lists of other actions."""
1250 self.list = list(map(list_of_actions, actionlist))
1251
1252
1253 self.varlist = ()
1254 self.targets = '$TARGETS'
1255
1257 return '\n'.join([a.genstring(target, source, env) for a in self.list])
1258
1260 return '\n'.join(map(str, self.list))
1261
1265
1267 """Return the signature contents of this action list.
1268
1269 Simple concatenation of the signatures of the elements.
1270 """
1271 return b"".join([bytes(x.get_contents(target, source, env)) for x in self.list])
1272
1284
1290
1291 - def get_varlist(self, target, source, env, executor=None):
1297
1298
1300 """A class for delaying calling an Action function with specific
1301 (positional and keyword) arguments until the Action is actually
1302 executed.
1303
1304 This class looks to the rest of the world like a normal Action object,
1305 but what it's really doing is hanging on to the arguments until we
1306 have a target, source and env to use for the expansion.
1307 """
1309 self.parent = parent
1310 self.args = args
1311 self.kw = kw
1312
1313 - def get_contents(self, target, source, env):
1314 actfunc = self.parent.actfunc
1315 try:
1316
1317 contents = actfunc.__code__.co_code
1318 except AttributeError:
1319
1320 try:
1321 contents = actfunc.__call__.__func__.__code__.co_code
1322 except AttributeError:
1323
1324
1325 contents = repr(actfunc)
1326
1327 return contents
1328
1329 - def subst(self, s, target, source, env):
1330
1331
1332 if is_List(s):
1333 result = []
1334 for elem in s:
1335 result.append(self.subst(elem, target, source, env))
1336 return self.parent.convert(result)
1337
1338
1339
1340
1341 if s == '$__env__':
1342 return env
1343 elif is_String(s):
1344 return env.subst(s, 1, target, source)
1345 return self.parent.convert(s)
1346
1348 return [self.subst(x, target, source, env) for x in self.args]
1349
1350 - def subst_kw(self, target, source, env):
1351 kw = {}
1352 for key in list(self.kw.keys()):
1353 kw[key] = self.subst(self.kw[key], target, source, env)
1354 return kw
1355
1356 - def __call__(self, target, source, env, executor=None):
1357 args = self.subst_args(target, source, env)
1358 kw = self.subst_kw(target, source, env)
1359 return self.parent.actfunc(*args, **kw)
1360
1362 args = self.subst_args(target, source, env)
1363 kw = self.subst_kw(target, source, env)
1364 return self.parent.strfunc(*args, **kw)
1365
1367 return self.parent.strfunc(*self.args, **self.kw)
1368
1369
1371 """A factory class that will wrap up an arbitrary function
1372 as an SCons-executable Action object.
1373
1374 The real heavy lifting here is done by the ActionCaller class.
1375 We just collect the (positional and keyword) arguments that we're
1376 called with and give them to the ActionCaller object we create,
1377 so it can hang onto them until it needs them.
1378 """
1379 - def __init__(self, actfunc, strfunc, convert=lambda x: x):
1380 self.actfunc = actfunc
1381 self.strfunc = strfunc
1382 self.convert = convert
1383
1388
1389
1390
1391
1392
1393
1394