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 2928 2008/04/29 22:44:09 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 xxx = map(lambda x: _object_contents(x.cell_contents), closure)
281 contents.append(',(' + string.join(xxx, ',') + ')')
282
283 return string.join(contents, '')
284
285
287
288
289
290 a1 = Action(act1)
291 a2 = Action(act2)
292 if a1 is None or a2 is None:
293 raise TypeError, "Cannot append %s to %s" % (type(act1), type(act2))
294 if isinstance(a1, ListAction):
295 if isinstance(a2, ListAction):
296 return ListAction(a1.list + a2.list)
297 else:
298 return ListAction(a1.list + [ a2 ])
299 else:
300 if isinstance(a2, ListAction):
301 return ListAction([ a1 ] + a2.list)
302 else:
303 return ListAction([ a1, a2 ])
304
306 """This is the actual "implementation" for the
307 Action factory method, below. This handles the
308 fact that passing lists to Action() itself has
309 different semantics than passing lists as elements
310 of lists.
311
312 The former will create a ListAction, the latter
313 will create a CommandAction by converting the inner
314 list elements to strings."""
315
316 if isinstance(act, ActionBase):
317 return act
318 if SCons.Util.is_List(act):
319 return apply(CommandAction, (act,)+args, kw)
320 if callable(act):
321 try:
322 gen = kw['generator']
323 del kw['generator']
324 except KeyError:
325 gen = 0
326 if gen:
327 action_type = CommandGeneratorAction
328 else:
329 action_type = FunctionAction
330 return apply(action_type, (act,)+args, kw)
331 if SCons.Util.is_String(act):
332 var=SCons.Util.get_environment_var(act)
333 if var:
334
335
336
337
338
339
340 return apply(LazyAction, (var,)+args, kw)
341 commands = string.split(str(act), '\n')
342 if len(commands) == 1:
343 return apply(CommandAction, (commands[0],)+args, kw)
344 else:
345 listCmdActions = map(lambda x, args=args, kw=kw:
346 apply(CommandAction, (x,)+args, kw),
347 commands)
348 return ListAction(listCmdActions)
349 return None
350
352 """A factory for action objects."""
353 if SCons.Util.is_List(act):
354 acts = map(lambda a, args=args, kw=kw:
355 apply(_do_create_action, (a,)+args, kw),
356 act)
357 acts = filter(None, acts)
358 if len(acts) == 1:
359 return acts[0]
360 else:
361 return ListAction(acts)
362 else:
363 return apply(_do_create_action, (act,)+args, kw)
364
366 """Base class for all types of action objects that can be held by
367 other objects (Builders, Executors, etc.) This provides the
368 common methods for manipulating and combining those actions."""
369
371 return cmp(self.__dict__, other)
372
375
377 return _actionAppend(self, other)
378
380 return _actionAppend(other, self)
381
383
384
385
386
387
388
389 self.presub_env = env
390 lines = string.split(str(self), '\n')
391 self.presub_env = None
392 return lines
393
394 - def get_executor(self, env, overrides, tlist, slist, executor_kw):
395 """Return the Executor for this Action."""
396 return SCons.Executor.Executor(self, env, overrides,
397 tlist, slist, executor_kw)
398
400 """Base class for actions that create output objects."""
401 - def __init__(self, strfunction=_null, presub=_null, chdir=None, exitstatfunc=None, **kw):
409
411 sys.stdout.write(s + "\n")
412
419 if not SCons.Util.is_List(target):
420 target = [target]
421 if not SCons.Util.is_List(source):
422 source = [source]
423
424 if exitstatfunc is _null: exitstatfunc = self.exitstatfunc
425 if presub is _null:
426 presub = self.presub
427 if presub is _null:
428 presub = print_actions_presub
429 if show is _null: show = print_actions
430 if execute is _null: execute = execute_actions
431 if chdir is _null: chdir = self.chdir
432 save_cwd = None
433 if chdir:
434 save_cwd = os.getcwd()
435 try:
436 chdir = str(chdir.abspath)
437 except AttributeError:
438 if not SCons.Util.is_String(chdir):
439 chdir = str(target[0].dir)
440 if presub:
441 t = string.join(map(str, target), ' and ')
442 l = string.join(self.presub_lines(env), '\n ')
443 out = "Building %s with action:\n %s\n" % (t, l)
444 sys.stdout.write(out)
445 s = None
446 if show and self.strfunction:
447 s = self.strfunction(target, source, env)
448 if s:
449 if chdir:
450 s = ('os.chdir(%s)\n' % repr(chdir)) + s
451 try:
452 get = env.get
453 except AttributeError:
454 print_func = self.print_cmd_line
455 else:
456 print_func = get('PRINT_CMD_LINE_FUNC')
457 if not print_func:
458 print_func = self.print_cmd_line
459 print_func(s, target, source, env)
460 stat = 0
461 if execute:
462 if chdir:
463 os.chdir(chdir)
464 try:
465 stat = self.execute(target, source, env)
466 if isinstance(stat, SCons.Errors.BuildError):
467 s = exitstatfunc(stat.status)
468 if s:
469 stat.status = s
470 else:
471 stat = s
472 else:
473 stat = exitstatfunc(stat)
474 finally:
475 if save_cwd:
476 os.chdir(save_cwd)
477 if s and save_cwd:
478 print_func('os.chdir(%s)' % repr(save_cwd), target, source, env)
479
480 return stat
481
482
484 """Takes a list of command line arguments and returns a pretty
485 representation for printing."""
486 cl = []
487 for arg in map(str, cmd_list):
488 if ' ' in arg or '\t' in arg:
489 arg = '"' + arg + '"'
490 cl.append(arg)
491 return string.join(cl)
492
494 """Class for command-execution actions."""
495 - def __init__(self, cmd, cmdstr=None, *args, **kw):
496
497
498
499
500
501
502
503
504
505 if __debug__: logInstanceCreation(self, 'Action.CommandAction')
506
507 if not cmdstr is None:
508 if callable(cmdstr):
509 args = (cmdstr,)+args
510 elif not SCons.Util.is_String(cmdstr):
511 raise SCons.Errors.UserError(\
512 'Invalid command display variable type. ' \
513 'You must either pass a string or a callback which ' \
514 'accepts (target, source, env) as parameters.')
515
516 apply(_ActionAction.__init__, (self,)+args, kw)
517 if SCons.Util.is_List(cmd):
518 if filter(SCons.Util.is_List, cmd):
519 raise TypeError, "CommandAction should be given only " \
520 "a single command"
521 self.cmd_list = cmd
522 self.cmdstr = cmdstr
523
525 if SCons.Util.is_List(self.cmd_list):
526 return string.join(map(str, self.cmd_list), ' ')
527 return str(self.cmd_list)
528
529 - def process(self, target, source, env):
530 result = env.subst_list(self.cmd_list, 0, target, source)
531 silent = None
532 ignore = None
533 while 1:
534 try: c = result[0][0][0]
535 except IndexError: c = None
536 if c == '@': silent = 1
537 elif c == '-': ignore = 1
538 else: break
539 result[0][0] = result[0][0][1:]
540 try:
541 if not result[0][0]:
542 result[0] = result[0][1:]
543 except IndexError:
544 pass
545 return result, ignore, silent
546
548 if not self.cmdstr is None:
549 from SCons.Subst import SUBST_RAW
550 c = env.subst(self.cmdstr, SUBST_RAW, target, source)
551 if c:
552 return c
553 cmd_list, ignore, silent = self.process(target, source, env)
554 if silent:
555 return ''
556 return _string_from_cmd_list(cmd_list[0])
557
558 - def execute(self, target, source, env):
559 """Execute a command action.
560
561 This will handle lists of commands as well as individual commands,
562 because construction variable substitution may turn a single
563 "command" into a list. This means that this class can actually
564 handle lists of commands, even though that's not how we use it
565 externally.
566 """
567 from SCons.Subst import escape_list
568 import SCons.Util
569 flatten_sequence = SCons.Util.flatten_sequence
570 is_String = SCons.Util.is_String
571 is_List = SCons.Util.is_List
572
573 try:
574 shell = env['SHELL']
575 except KeyError:
576 raise SCons.Errors.UserError('Missing SHELL construction variable.')
577
578 try:
579 spawn = env['SPAWN']
580 except KeyError:
581 raise SCons.Errors.UserError('Missing SPAWN construction variable.')
582 else:
583 if is_String(spawn):
584 spawn = env.subst(spawn, raw=1, conv=lambda x: x)
585
586 escape = env.get('ESCAPE', lambda x: x)
587
588 try:
589 ENV = env['ENV']
590 except KeyError:
591 global default_ENV
592 if not default_ENV:
593 import SCons.Environment
594 default_ENV = SCons.Environment.Environment()['ENV']
595 ENV = default_ENV
596
597
598 for key, value in ENV.items():
599 if not is_String(value):
600 if is_List(value):
601
602
603
604 value = flatten_sequence(value)
605 ENV[key] = string.join(map(str, value), os.pathsep)
606 else:
607
608
609
610
611 ENV[key] = str(value)
612
613 cmd_list, ignore, silent = self.process(target, map(rfile, source), env)
614
615
616 for cmd_line in filter(len, cmd_list):
617
618 cmd_line = escape_list(cmd_line, escape)
619 result = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
620 if not ignore and result:
621 msg = "Error %s" % result
622 return SCons.Errors.BuildError(errstr=msg,
623 status=result,
624 action=self,
625 command=cmd_line)
626 return 0
627
628 - def get_contents(self, target, source, env):
629 """Return the signature contents of this action's command line.
630
631 This strips $(-$) and everything in between the string,
632 since those parts don't affect signatures.
633 """
634 from SCons.Subst import SUBST_SIG
635 cmd = self.cmd_list
636 if SCons.Util.is_List(cmd):
637 cmd = string.join(map(str, cmd))
638 else:
639 cmd = str(cmd)
640 return env.subst_target_source(cmd, SUBST_SIG, target, source)
641
643 icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True)
644 if SCons.Util.is_String(icd) and icd[:1] == '$':
645 icd = env.subst(icd)
646 if not icd or icd in ('0', 'None'):
647 return []
648 from SCons.Subst import SUBST_SIG
649 cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, target, source)
650 res = []
651 for cmd_line in cmd_list:
652 if cmd_line:
653 d = env.WhereIs(str(cmd_line[0]))
654 if d:
655 res.append(env.fs.File(d))
656 return res
657
659 """Class for command-generator actions."""
660 - def __init__(self, generator, *args, **kw):
661 if __debug__: logInstanceCreation(self, 'Action.CommandGeneratorAction')
662 self.generator = generator
663 self.gen_args = args
664 self.gen_kw = kw
665
666 - def _generate(self, target, source, env, for_signature):
667
668
669 if not SCons.Util.is_List(target):
670 target = [target]
671
672 ret = self.generator(target=target, source=source, env=env, for_signature=for_signature)
673 gen_cmd = apply(Action, (ret,)+self.gen_args, self.gen_kw)
674 if not gen_cmd:
675 raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
676 return gen_cmd
677
679 try:
680 env = self.presub_env
681 except AttributeError:
682 env = None
683 if env is None:
684 env = SCons.Defaults.DefaultEnvironment()
685 act = self._generate([], [], env, 1)
686 return str(act)
687
690
693 act = self._generate(target, source, env, 0)
694 return act(target, source, env, exitstatfunc, presub,
695 show, execute, chdir)
696
697 - def get_contents(self, target, source, env):
698 """Return the signature contents of this action's command line.
699
700 This strips $(-$) and everything in between the string,
701 since those parts don't affect signatures.
702 """
703 return self._generate(target, source, env, 1).get_contents(target, source, env)
704
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727 -class LazyAction(CommandGeneratorAction, CommandAction):
728
735
741
743 c = env.get(self.var, '')
744 gen_cmd = apply(Action, (c,)+self.gen_args, self.gen_kw)
745 if not gen_cmd:
746 raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c)))
747 return gen_cmd
748
749 - def _generate(self, target, source, env, for_signature):
751
752 - def __call__(self, target, source, env, *args, **kw):
756
757 - def get_contents(self, target, source, env):
758 c = self.get_parent_class(env)
759 return c.get_contents(self, target, source, env)
760
761
762
764 """Class for Python function actions."""
765
767 if __debug__: logInstanceCreation(self, 'Action.FunctionAction')
768
769 if not cmdstr is _null:
770 if callable(cmdstr):
771 args = (cmdstr,)+args
772 elif not (cmdstr is None or SCons.Util.is_String(cmdstr)):
773 raise SCons.Errors.UserError(\
774 'Invalid function display variable type. ' \
775 'You must either pass a string or a callback which ' \
776 'accepts (target, source, env) as parameters.')
777
778 self.execfunction = execfunction
779 try:
780 self.funccontents = _callable_contents(execfunction)
781 except AttributeError:
782 try:
783
784 self.gc = execfunction.get_contents
785 except AttributeError:
786
787 self.funccontents = _object_contents(execfunction)
788
789 apply(_ActionAction.__init__, (self,)+args, kw)
790 self.varlist = kw.get('varlist', [])
791 self.cmdstr = cmdstr
792
794 try:
795 return self.execfunction.__name__
796 except AttributeError:
797 try:
798 return self.execfunction.__class__.__name__
799 except AttributeError:
800 return "unknown_python_function"
801
803 if self.cmdstr is None:
804 return None
805 if not self.cmdstr is _null:
806 from SCons.Subst import SUBST_RAW
807 c = env.subst(self.cmdstr, SUBST_RAW, target, source)
808 if c:
809 return c
810 def array(a):
811 def quote(s):
812 return '"' + str(s) + '"'
813 return '[' + string.join(map(quote, a), ", ") + ']'
814 try:
815 strfunc = self.execfunction.strfunction
816 except AttributeError:
817 pass
818 else:
819 if strfunc is None:
820 return None
821 if callable(strfunc):
822 return strfunc(target, source, env)
823 name = self.function_name()
824 tstr = array(target)
825 sstr = array(source)
826 return "%s(%s, %s)" % (name, tstr, sstr)
827
829 name = self.function_name()
830 if name == 'ActionCaller':
831 return str(self.execfunction)
832 return "%s(target, source, env)" % name
833
834 - def execute(self, target, source, env):
835 rsources = map(rfile, source)
836 try:
837 result = self.execfunction(target=target, source=rsources, env=env)
838 except EnvironmentError, e:
839
840
841
842
843
844 try: filename = e.filename
845 except AttributeError: filename = None
846 result = SCons.Errors.BuildError(node=target,
847 errstr=e.strerror,
848 status=1,
849 filename=filename,
850 action=self,
851 command=self.strfunction(target, source, env))
852 else:
853 if result:
854 msg = "Error %s" % result
855 result = SCons.Errors.BuildError(errstr=msg,
856 status=result,
857 action=self,
858 command=self.strfunction(target, source, env))
859 return result
860
861 - def get_contents(self, target, source, env):
862 """Return the signature contents of this callable action."""
863 try:
864 contents = self.gc(target, source, env)
865 except AttributeError:
866 contents = self.funccontents
867
868 result = [contents]
869 for v in self.varlist:
870 result.append(env.subst('${'+v+'}'))
871
872 return string.join(result, '')
873
876
878 """Class for lists of other actions."""
885 self.list = map(list_of_actions, list)
886
888 return string.join(map(lambda a, t=target, s=source, e=env:
889 a.genstring(t, s, e),
890 self.list),
891 '\n')
892
894 return string.join(map(str, self.list), '\n')
895
899
900 - def get_contents(self, target, source, env):
901 """Return the signature contents of this action list.
902
903 Simple concatenation of the signatures of the elements.
904 """
905 return string.join(map(lambda x, t=target, s=source, e=env:
906 x.get_contents(t, s, e),
907 self.list),
908 "")
909
912 for act in self.list:
913 stat = act(target, source, env, exitstatfunc, presub,
914 show, execute, chdir)
915 if stat:
916 return stat
917 return 0
918
924
926 """A class for delaying calling an Action function with specific
927 (positional and keyword) arguments until the Action is actually
928 executed.
929
930 This class looks to the rest of the world like a normal Action object,
931 but what it's really doing is hanging on to the arguments until we
932 have a target, source and env to use for the expansion.
933 """
935 self.parent = parent
936 self.args = args
937 self.kw = kw
938 - def get_contents(self, target, source, env):
939 actfunc = self.parent.actfunc
940 try:
941
942 contents = str(actfunc.func_code.co_code)
943 except AttributeError:
944
945 try:
946 contents = str(actfunc.__call__.im_func.func_code.co_code)
947 except AttributeError:
948
949
950 contents = str(actfunc)
951 contents = remove_set_lineno_codes(contents)
952 return contents
953 - def subst(self, s, target, source, env):
971 return map(lambda x, self=self, t=target, s=source, e=env:
972 self.subst(x, t, s, e),
973 self.args)
974 - def subst_kw(self, target, source, env):
975 kw = {}
976 for key in self.kw.keys():
977 kw[key] = self.subst(self.kw[key], target, source, env)
978 return kw
979 - def __call__(self, target, source, env):
980 args = self.subst_args(target, source, env)
981 kw = self.subst_kw(target, source, env)
982 return apply(self.parent.actfunc, args, kw)
984 args = self.subst_args(target, source, env)
985 kw = self.subst_kw(target, source, env)
986 return apply(self.parent.strfunc, args, kw)
988 return apply(self.parent.strfunc, self.args, self.kw)
989
991 """A factory class that will wrap up an arbitrary function
992 as an SCons-executable Action object.
993
994 The real heavy lifting here is done by the ActionCaller class.
995 We just collect the (positional and keyword) arguments that we're
996 called with and give them to the ActionCaller object we create,
997 so it can hang onto them until it needs them.
998 """
999 - def __init__(self, actfunc, strfunc, convert=lambda x: x):
1000 self.actfunc = actfunc
1001 self.strfunc = strfunc
1002 self.convert = convert
1007