1 """SCons.Action
2
3 This encapsulates information about executing any sort of action that
4 can build one or more target Nodes (typically files) from one or more
5 source Nodes (also typically files) given a specific Environment.
6
7 The base class here is ActionBase. The base class supplies just a few
8 OO utility methods and some generic methods for displaying information
9 about an Action in response to the various commands that control printing.
10
11 A second-level base class is _ActionAction. This extends ActionBase
12 by providing the methods that can be used to show and perform an
13 action. True Action objects will subclass _ActionAction; Action
14 factory class objects will subclass ActionBase.
15
16 The heavy lifting is handled by subclasses for the different types of
17 actions we might execute:
18
19 CommandAction
20 CommandGeneratorAction
21 FunctionAction
22 ListAction
23
24 The subclasses supply the following public interface methods used by
25 other modules:
26
27 __call__()
28 THE public interface, "calling" an Action object executes the
29 command or Python function. This also takes care of printing
30 a pre-substitution command for debugging purposes.
31
32 get_contents()
33 Fetches the "contents" of an Action for signature calculation.
34 This is what gets MD5 checksumm'ed to decide if a target needs
35 to be rebuilt because its action changed.
36
37 genstring()
38 Returns a string representation of the Action *without*
39 command substitution, but allows a CommandGeneratorAction to
40 generate the right action based on the specified target,
41 source and env. This is used by the Signature subsystem
42 (through the Executor) to obtain an (imprecise) representation
43 of the Action operation for informative purposes.
44
45
46 Subclasses also supply the following methods for internal use within
47 this module:
48
49 __str__()
50 Returns a string approximation of the Action; no variable
51 substitution is performed.
52
53 execute()
54 The internal method that really, truly, actually handles the
55 execution of a command or Python function. This is used so
56 that the __call__() methods can take care of displaying any
57 pre-substitution representations, and *then* execute an action
58 without worrying about the specific Actions involved.
59
60 strfunction()
61 Returns a substituted string representation of the Action.
62 This is used by the _ActionAction.show() command to display the
63 command/function that will be executed to generate the target(s).
64
65 There is a related independent ActionCaller class that looks like a
66 regular Action, and which serves as a wrapper for arbitrary functions
67 that we want to let the user specify the arguments to now, but actually
68 execute later (when an out-of-date check determines that it's needed to
69 be executed, for example). Objects of this class are returned by an
70 ActionFactory class that provides a __call__() method as a convenient
71 way for wrapping up the functions.
72
73 """
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98 __revision__ = "src/engine/SCons/Action.py 3266 2008/08/12 07:31:01 knight"
99
100 import cPickle
101 import dis
102 import os
103 import os.path
104 import string
105 import sys
106
107 from SCons.Debug import logInstanceCreation
108 import SCons.Errors
109 import SCons.Executor
110 import SCons.Util
111
114
115 _null = _Null
116
117 print_actions = 1
118 execute_actions = 1
119 print_actions_presub = 0
120
121 default_ENV = None
122
124 try:
125 return n.rfile()
126 except AttributeError:
127 return n
128
131
132 try:
133 SET_LINENO = dis.SET_LINENO
134 HAVE_ARGUMENT = dis.HAVE_ARGUMENT
135 except AttributeError:
136 remove_set_lineno_codes = lambda x: x
137 else:
153
154
156 """Return the signature contents of a callable Python object.
157 """
158 try:
159
160 return _function_contents(obj.im_func)
161
162 except AttributeError:
163 try:
164
165 return _function_contents(obj.__call__.im_func)
166
167 except AttributeError:
168 try:
169
170 return _code_contents(obj)
171
172 except AttributeError:
173
174 return _function_contents(obj)
175
176
178 """Return the signature contents of any Python object.
179
180 We have to handle the case where object contains a code object
181 since it can be pickled directly.
182 """
183 try:
184
185 return _function_contents(obj.im_func)
186
187 except AttributeError:
188 try:
189
190 return _function_contents(obj.__call__.im_func)
191
192 except AttributeError:
193 try:
194
195 return _code_contents(obj)
196
197 except AttributeError:
198 try:
199
200 return _function_contents(obj)
201
202 except AttributeError:
203
204 try:
205 return cPickle.dumps(obj)
206 except (cPickle.PicklingError, TypeError):
207
208
209
210
211
212 return str(obj)
213
214
215 -def _code_contents(code):
216 """Return the signature contents of a code object.
217
218 By providing direct access to the code object of the
219 function, Python makes this extremely easy. Hooray!
220
221 Unfortunately, older versions of Python include line
222 number indications in the compiled byte code. Boo!
223 So we remove the line number byte codes to prevent
224 recompilations from moving a Python function.
225 """
226
227 contents = []
228
229
230
231 contents.append("%s,%s" % (code.co_argcount, len(code.co_varnames)))
232 try:
233 contents.append(",%s,%s" % (len(code.co_cellvars), len(code.co_freevars)))
234 except AttributeError:
235
236 contents.append(",0,0")
237
238
239
240
241
242
243
244
245
246 contents.append(',(' + string.join(map(_object_contents,code.co_consts[1:]),',') + ')')
247
248
249
250
251
252 contents.append(',(' + string.join(map(_object_contents,code.co_names),',') + ')')
253
254
255
256 contents.append(',(' + str(remove_set_lineno_codes(code.co_code)) + ')')
257
258 return string.join(contents, '')
259
260
262 """Return the signature contents of a function."""
263
264 contents = [_code_contents(func.func_code)]
265
266
267 if func.func_defaults:
268 contents.append(',(' + string.join(map(_object_contents,func.func_defaults),',') + ')')
269 else:
270 contents.append(',()')
271
272
273 try:
274 closure = func.func_closure or []
275 except AttributeError:
276
277 closure = []
278
279
280 try:
281 xxx = map(lambda x: _object_contents(x.cell_contents), closure)
282 except AttributeError:
283 xxx = []
284 contents.append(',(' + string.join(xxx, ',') + ')')
285
286 return string.join(contents, '')
287
288
290
291
292
293 a1 = Action(act1)
294 a2 = Action(act2)
295 if a1 is None or a2 is None:
296 raise TypeError, "Cannot append %s to %s" % (type(act1), type(act2))
297 if isinstance(a1, ListAction):
298 if isinstance(a2, ListAction):
299 return ListAction(a1.list + a2.list)
300 else:
301 return ListAction(a1.list + [ a2 ])
302 else:
303 if isinstance(a2, ListAction):
304 return ListAction([ a1 ] + a2.list)
305 else:
306 return ListAction([ a1, a2 ])
307
309 """This is the actual "implementation" for the
310 Action factory method, below. This handles the
311 fact that passing lists to Action() itself has
312 different semantics than passing lists as elements
313 of lists.
314
315 The former will create a ListAction, the latter
316 will create a CommandAction by converting the inner
317 list elements to strings."""
318
319 if isinstance(act, ActionBase):
320 return act
321 if SCons.Util.is_List(act):
322 return apply(CommandAction, (act,)+args, kw)
323 if callable(act):
324 try:
325 gen = kw['generator']
326 del kw['generator']
327 except KeyError:
328 gen = 0
329 if gen:
330 action_type = CommandGeneratorAction
331 else:
332 action_type = FunctionAction
333 return apply(action_type, (act,)+args, kw)
334 if SCons.Util.is_String(act):
335 var=SCons.Util.get_environment_var(act)
336 if var:
337
338
339
340
341
342
343 return apply(LazyAction, (var,)+args, kw)
344 commands = string.split(str(act), '\n')
345 if len(commands) == 1:
346 return apply(CommandAction, (commands[0],)+args, kw)
347 else:
348 listCmdActions = map(lambda x, args=args, kw=kw:
349 apply(CommandAction, (x,)+args, kw),
350 commands)
351 return ListAction(listCmdActions)
352 return None
353
355 """A factory for action objects."""
356 if SCons.Util.is_List(act):
357 acts = map(lambda a, args=args, kw=kw:
358 apply(_do_create_action, (a,)+args, kw),
359 act)
360 acts = filter(None, acts)
361 if len(acts) == 1:
362 return acts[0]
363 else:
364 return ListAction(acts)
365 else:
366 return apply(_do_create_action, (act,)+args, kw)
367
369 """Base class for all types of action objects that can be held by
370 other objects (Builders, Executors, etc.) This provides the
371 common methods for manipulating and combining those actions."""
372
374 return cmp(self.__dict__, other)
375
378
380 return _actionAppend(self, other)
381
383 return _actionAppend(other, self)
384
386
387
388
389
390
391
392 self.presub_env = env
393 lines = string.split(str(self), '\n')
394 self.presub_env = None
395 return lines
396
397 - def get_executor(self, env, overrides, tlist, slist, executor_kw):
398 """Return the Executor for this Action."""
399 return SCons.Executor.Executor(self, env, overrides,
400 tlist, slist, executor_kw)
401
403 """Base class for actions that create output objects."""
404 - def __init__(self, strfunction=_null, presub=_null, chdir=None, exitstatfunc=None, **kw):
412
414 sys.stdout.write(s + "\n")
415
422 if not SCons.Util.is_List(target):
423 target = [target]
424 if not SCons.Util.is_List(source):
425 source = [source]
426
427 if exitstatfunc is _null: exitstatfunc = self.exitstatfunc
428 if presub is _null:
429 presub = self.presub
430 if presub is _null:
431 presub = print_actions_presub
432 if show is _null: show = print_actions
433 if execute is _null: execute = execute_actions
434 if chdir is _null: chdir = self.chdir
435 save_cwd = None
436 if chdir:
437 save_cwd = os.getcwd()
438 try:
439 chdir = str(chdir.abspath)
440 except AttributeError:
441 if not SCons.Util.is_String(chdir):
442 chdir = str(target[0].dir)
443 if presub:
444 t = string.join(map(str, target), ' and ')
445 l = string.join(self.presub_lines(env), '\n ')
446 out = "Building %s with action:\n %s\n" % (t, l)
447 sys.stdout.write(out)
448 s = None
449 if show and self.strfunction:
450 s = self.strfunction(target, source, env)
451 if s:
452 if chdir:
453 s = ('os.chdir(%s)\n' % repr(chdir)) + s
454 try:
455 get = env.get
456 except AttributeError:
457 print_func = self.print_cmd_line
458 else:
459 print_func = get('PRINT_CMD_LINE_FUNC')
460 if not print_func:
461 print_func = self.print_cmd_line
462 print_func(s, target, source, env)
463 stat = 0
464 if execute:
465 if chdir:
466 os.chdir(chdir)
467 try:
468 stat = self.execute(target, source, env)
469 if isinstance(stat, SCons.Errors.BuildError):
470 s = exitstatfunc(stat.status)
471 if s:
472 stat.status = s
473 else:
474 stat = s
475 else:
476 stat = exitstatfunc(stat)
477 finally:
478 if save_cwd:
479 os.chdir(save_cwd)
480 if s and save_cwd:
481 print_func('os.chdir(%s)' % repr(save_cwd), target, source, env)
482
483 return stat
484
485
487 """Takes a list of command line arguments and returns a pretty
488 representation for printing."""
489 cl = []
490 for arg in map(str, cmd_list):
491 if ' ' in arg or '\t' in arg:
492 arg = '"' + arg + '"'
493 cl.append(arg)
494 return string.join(cl)
495
497 """Class for command-execution actions."""
498 - def __init__(self, cmd, cmdstr=None, *args, **kw):
499
500
501
502
503
504
505
506
507
508 if __debug__: logInstanceCreation(self, 'Action.CommandAction')
509
510 if not cmdstr is None:
511 if callable(cmdstr):
512 args = (cmdstr,)+args
513 elif not SCons.Util.is_String(cmdstr):
514 raise SCons.Errors.UserError(\
515 'Invalid command display variable type. ' \
516 'You must either pass a string or a callback which ' \
517 'accepts (target, source, env) as parameters.')
518
519 apply(_ActionAction.__init__, (self,)+args, kw)
520 if SCons.Util.is_List(cmd):
521 if filter(SCons.Util.is_List, cmd):
522 raise TypeError, "CommandAction should be given only " \
523 "a single command"
524 self.cmd_list = cmd
525 self.cmdstr = cmdstr
526
528 if SCons.Util.is_List(self.cmd_list):
529 return string.join(map(str, self.cmd_list), ' ')
530 return str(self.cmd_list)
531
532 - def process(self, target, source, env):
533 result = env.subst_list(self.cmd_list, 0, target, source)
534 silent = None
535 ignore = None
536 while 1:
537 try: c = result[0][0][0]
538 except IndexError: c = None
539 if c == '@': silent = 1
540 elif c == '-': ignore = 1
541 else: break
542 result[0][0] = result[0][0][1:]
543 try:
544 if not result[0][0]:
545 result[0] = result[0][1:]
546 except IndexError:
547 pass
548 return result, ignore, silent
549
551 if not self.cmdstr is None:
552 from SCons.Subst import SUBST_RAW
553 c = env.subst(self.cmdstr, SUBST_RAW, target, source)
554 if c:
555 return c
556 cmd_list, ignore, silent = self.process(target, source, env)
557 if silent:
558 return ''
559 return _string_from_cmd_list(cmd_list[0])
560
561 - def execute(self, target, source, env):
562 """Execute a command action.
563
564 This will handle lists of commands as well as individual commands,
565 because construction variable substitution may turn a single
566 "command" into a list. This means that this class can actually
567 handle lists of commands, even though that's not how we use it
568 externally.
569 """
570 from SCons.Subst import escape_list
571 import SCons.Util
572 flatten_sequence = SCons.Util.flatten_sequence
573 is_String = SCons.Util.is_String
574 is_List = SCons.Util.is_List
575
576 try:
577 shell = env['SHELL']
578 except KeyError:
579 raise SCons.Errors.UserError('Missing SHELL construction variable.')
580
581 try:
582 spawn = env['SPAWN']
583 except KeyError:
584 raise SCons.Errors.UserError('Missing SPAWN construction variable.')
585 else:
586 if is_String(spawn):
587 spawn = env.subst(spawn, raw=1, conv=lambda x: x)
588
589 escape = env.get('ESCAPE', lambda x: x)
590
591 try:
592 ENV = env['ENV']
593 except KeyError:
594 global default_ENV
595 if not default_ENV:
596 import SCons.Environment
597 default_ENV = SCons.Environment.Environment()['ENV']
598 ENV = default_ENV
599
600
601 for key, value in ENV.items():
602 if not is_String(value):
603 if is_List(value):
604
605
606
607 value = flatten_sequence(value)
608 ENV[key] = string.join(map(str, value), os.pathsep)
609 else:
610
611
612
613
614 ENV[key] = str(value)
615
616 cmd_list, ignore, silent = self.process(target, map(rfile, source), env)
617
618
619 for cmd_line in filter(len, cmd_list):
620
621 cmd_line = escape_list(cmd_line, escape)
622 result = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
623 if not ignore and result:
624 msg = "Error %s" % result
625 return SCons.Errors.BuildError(errstr=msg,
626 status=result,
627 action=self,
628 command=cmd_line)
629 return 0
630
631 - def get_contents(self, target, source, env):
632 """Return the signature contents of this action's command line.
633
634 This strips $(-$) and everything in between the string,
635 since those parts don't affect signatures.
636 """
637 from SCons.Subst import SUBST_SIG
638 cmd = self.cmd_list
639 if SCons.Util.is_List(cmd):
640 cmd = string.join(map(str, cmd))
641 else:
642 cmd = str(cmd)
643 return env.subst_target_source(cmd, SUBST_SIG, target, source)
644
646 icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True)
647 if SCons.Util.is_String(icd) and icd[:1] == '$':
648 icd = env.subst(icd)
649 if not icd or icd in ('0', 'None'):
650 return []
651 from SCons.Subst import SUBST_SIG
652 cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, target, source)
653 res = []
654 for cmd_line in cmd_list:
655 if cmd_line:
656 d = env.WhereIs(str(cmd_line[0]))
657 if d:
658 res.append(env.fs.File(d))
659 return res
660
662 """Class for command-generator actions."""
663 - def __init__(self, generator, *args, **kw):
664 if __debug__: logInstanceCreation(self, 'Action.CommandGeneratorAction')
665 self.generator = generator
666 self.gen_args = args
667 self.gen_kw = kw
668
669 - def _generate(self, target, source, env, for_signature):
670
671
672 if not SCons.Util.is_List(target):
673 target = [target]
674
675 ret = self.generator(target=target, source=source, env=env, for_signature=for_signature)
676 gen_cmd = apply(Action, (ret,)+self.gen_args, self.gen_kw)
677 if not gen_cmd:
678 raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
679 return gen_cmd
680
682 try:
683 env = self.presub_env
684 except AttributeError:
685 env = None
686 if env is None:
687 env = SCons.Defaults.DefaultEnvironment()
688 act = self._generate([], [], env, 1)
689 return str(act)
690
693
696 act = self._generate(target, source, env, 0)
697 return act(target, source, env, exitstatfunc, presub,
698 show, execute, chdir)
699
700 - def get_contents(self, target, source, env):
701 """Return the signature contents of this action's command line.
702
703 This strips $(-$) and everything in between the string,
704 since those parts don't affect signatures.
705 """
706 return self._generate(target, source, env, 1).get_contents(target, source, env)
707
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730 -class LazyAction(CommandGeneratorAction, CommandAction):
731
738
744
746 c = env.get(self.var, '')
747 gen_cmd = apply(Action, (c,)+self.gen_args, self.gen_kw)
748 if not gen_cmd:
749 raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c)))
750 return gen_cmd
751
752 - def _generate(self, target, source, env, for_signature):
754
755 - def __call__(self, target, source, env, *args, **kw):
759
760 - def get_contents(self, target, source, env):
761 c = self.get_parent_class(env)
762 return c.get_contents(self, target, source, env)
763
764
765
767 """Class for Python function actions."""
768
770 if __debug__: logInstanceCreation(self, 'Action.FunctionAction')
771
772 if not cmdstr is _null:
773 if callable(cmdstr):
774 args = (cmdstr,)+args
775 elif not (cmdstr is None or SCons.Util.is_String(cmdstr)):
776 raise SCons.Errors.UserError(\
777 'Invalid function display variable type. ' \
778 'You must either pass a string or a callback which ' \
779 'accepts (target, source, env) as parameters.')
780
781 self.execfunction = execfunction
782 try:
783 self.funccontents = _callable_contents(execfunction)
784 except AttributeError:
785 try:
786
787 self.gc = execfunction.get_contents
788 except AttributeError:
789
790 self.funccontents = _object_contents(execfunction)
791
792 apply(_ActionAction.__init__, (self,)+args, kw)
793 self.varlist = kw.get('varlist', [])
794 if SCons.Util.is_String(self.varlist):
795
796 self.varlist=[self.varlist]
797 self.cmdstr = cmdstr
798
800 try:
801 return self.execfunction.__name__
802 except AttributeError:
803 try:
804 return self.execfunction.__class__.__name__
805 except AttributeError:
806 return "unknown_python_function"
807
825 return '[' + string.join(map(quote, a), ", ") + ']'
826 try:
827 strfunc = self.execfunction.strfunction
828 except AttributeError:
829 pass
830 else:
831 if strfunc is None:
832 return None
833 if callable(strfunc):
834 return strfunc(target, source, env)
835 name = self.function_name()
836 tstr = array(target)
837 sstr = array(source)
838 return "%s(%s, %s)" % (name, tstr, sstr)
839
841 name = self.function_name()
842 if name == 'ActionCaller':
843 return str(self.execfunction)
844 return "%s(target, source, env)" % name
845
846 - def execute(self, target, source, env):
847 rsources = map(rfile, source)
848 try:
849 result = self.execfunction(target=target, source=rsources, env=env)
850 except EnvironmentError, e:
851
852
853
854
855
856 try: filename = e.filename
857 except AttributeError: filename = None
858 result = SCons.Errors.BuildError(node=target,
859 errstr=e.strerror,
860 status=1,
861 filename=filename,
862 action=self,
863 command=self.strfunction(target, source, env))
864 else:
865 if result:
866 msg = "Error %s" % result
867 result = SCons.Errors.BuildError(errstr=msg,
868 status=result,
869 action=self,
870 command=self.strfunction(target, source, env))
871 return result
872
873 - def get_contents(self, target, source, env):
874 """Return the signature contents of this callable action."""
875 try:
876 contents = self.gc(target, source, env)
877 except AttributeError:
878 contents = self.funccontents
879
880 result = [contents]
881 for v in self.varlist:
882 result.append(env.subst('${'+v+'}'))
883
884 return string.join(result, '')
885
888
890 """Class for lists of other actions."""
897 self.list = map(list_of_actions, list)
898
900 return string.join(map(lambda a, t=target, s=source, e=env:
901 a.genstring(t, s, e),
902 self.list),
903 '\n')
904
906 return string.join(map(str, self.list), '\n')
907
911
912 - def get_contents(self, target, source, env):
913 """Return the signature contents of this action list.
914
915 Simple concatenation of the signatures of the elements.
916 """
917 return string.join(map(lambda x, t=target, s=source, e=env:
918 x.get_contents(t, s, e),
919 self.list),
920 "")
921
924 for act in self.list:
925 stat = act(target, source, env, exitstatfunc, presub,
926 show, execute, chdir)
927 if stat:
928 return stat
929 return 0
930
936
938 """A class for delaying calling an Action function with specific
939 (positional and keyword) arguments until the Action is actually
940 executed.
941
942 This class looks to the rest of the world like a normal Action object,
943 but what it's really doing is hanging on to the arguments until we
944 have a target, source and env to use for the expansion.
945 """
947 self.parent = parent
948 self.args = args
949 self.kw = kw
950 - def get_contents(self, target, source, env):
951 actfunc = self.parent.actfunc
952 try:
953
954 contents = str(actfunc.func_code.co_code)
955 except AttributeError:
956
957 try:
958 contents = str(actfunc.__call__.im_func.func_code.co_code)
959 except AttributeError:
960
961
962 contents = str(actfunc)
963 contents = remove_set_lineno_codes(contents)
964 return contents
965 - def subst(self, s, target, source, env):
983 return map(lambda x, self=self, t=target, s=source, e=env:
984 self.subst(x, t, s, e),
985 self.args)
986 - def subst_kw(self, target, source, env):
987 kw = {}
988 for key in self.kw.keys():
989 kw[key] = self.subst(self.kw[key], target, source, env)
990 return kw
991 - def __call__(self, target, source, env):
992 args = self.subst_args(target, source, env)
993 kw = self.subst_kw(target, source, env)
994 return apply(self.parent.actfunc, args, kw)
996 args = self.subst_args(target, source, env)
997 kw = self.subst_kw(target, source, env)
998 return apply(self.parent.strfunc, args, kw)
1000 return apply(self.parent.strfunc, self.args, self.kw)
1001
1003 """A factory class that will wrap up an arbitrary function
1004 as an SCons-executable Action object.
1005
1006 The real heavy lifting here is done by the ActionCaller class.
1007 We just collect the (positional and keyword) arguments that we're
1008 called with and give them to the ActionCaller object we create,
1009 so it can hang onto them until it needs them.
1010 """
1011 - def __init__(self, actfunc, strfunc, convert=lambda x: x):
1012 self.actfunc = actfunc
1013 self.strfunc = strfunc
1014 self.convert = convert
1019