Split Action objects into their own module.
[scons.git] / src / engine / SCons / Action.py
1 """engine.SCons.Action
2
3 XXX
4
5 """
6
7 #
8 # Copyright (c) 2001 Steven Knight
9 #
10 # Permission is hereby granted, free of charge, to any person obtaining
11 # a copy of this software and associated documentation files (the
12 # "Software"), to deal in the Software without restriction, including
13 # without limitation the rights to use, copy, modify, merge, publish,
14 # distribute, sublicense, and/or sell copies of the Software, and to
15 # permit persons to whom the Software is furnished to do so, subject to
16 # the following conditions:
17 #
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
20 #
21 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
22 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
23 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 #
29
30 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
31
32 import os
33 import os.path
34 import string
35 import sys
36
37 import SCons.Util
38
39 print_actions = 1;
40 execute_actions = 1;
41
42 exitvalmap = {
43     2 : 127,
44     13 : 126,
45 }
46
47 if os.name == 'posix':
48
49     def spawn(cmd, args, env):
50         pid = os.fork()
51         if not pid:
52             # Child process.
53             exitval = 127
54             try:
55                 os.execvpe(cmd, args, env)
56             except OSError, e:
57                 exitval = exitvalmap[e[0]]
58                 sys.stderr.write("scons: %s: %s\n" % (cmd, e[1]))
59             os._exit(exitval)
60         else:
61             # Parent process.
62             pid, stat = os.waitpid(pid, 0)
63             ret = stat >> 8
64             return ret
65
66 elif os.name == 'nt':
67
68     def pathsearch(cmd, env):
69         # In order to deal with the fact that 1.5.2 doesn't have
70         # os.spawnvpe(), roll our own PATH search.
71         if os.path.isabs(cmd):
72             if not os.path.exists(cmd):
73                 exts = env['PATHEXT']
74                 if not SCons.Util.is_List(exts):
75                     exts = string.split(exts, os.pathsep)
76                 for e in exts:
77                     f = cmd + e
78                     if os.path.exists(f):
79                         return f
80         else:
81             path = env['PATH']
82             if not SCons.Util.is_List(path):
83                 path = string.split(path, os.pathsep)
84             exts = env['PATHEXT']
85             if not SCons.Util.is_List(exts):
86                 exts = string.split(exts, os.pathsep)
87             pairs = []
88             for dir in path:
89                 for e in exts:
90                     pairs.append((dir, e))
91             for dir, ext in pairs:
92                 f = os.path.join(dir, cmd)
93                 if not ext is None:
94                     f = f + ext
95                 if os.path.exists(f):
96                     return f
97         return cmd
98
99     def spawn(cmd, args, env):
100         try:
101             try:
102                 ret = os.spawnvpe(os.P_WAIT, cmd, args, env)
103             except AttributeError:
104                 cmd = pathsearch(cmd, env)
105                 ret = os.spawnve(os.P_WAIT, cmd, args, env)
106         except OSError, e:
107             ret = exitvalmap[e[0]]
108             sys.stderr.write("scons: %s: %s\n" % (cmd, e[1]))
109         return ret
110
111 def Action(act):
112     """A factory for action objects."""
113     if isinstance(act, ActionBase):
114         return act
115     elif callable(act):
116         return FunctionAction(act)
117     elif SCons.Util.is_String(act):
118         return CommandAction(act)
119     elif SCons.Util.is_List(act):
120         return ListAction(act)
121     else:
122         return None
123     
124 class ActionBase:
125     """Base class for actions that create output objects."""
126     def __cmp__(self, other):
127         return cmp(self.__dict__, other.__dict__)
128
129     def show(self, string):
130         print string
131
132     def subst_dict(self, **kw):
133         """Create a dictionary for substitution of construction
134         variables.
135
136         This translates the following special arguments:
137
138             env    - the construction environment itself,
139                      the values of which (CC, CCFLAGS, etc.)
140                      are copied straight into the dictionary
141
142             target - the target (object or array of objects),
143                      used to generate the TARGET and TARGETS
144                      construction variables
145
146             source - the source (object or array of objects),
147                      used to generate the SOURCES construction
148                      variable
149
150         Any other keyword arguments are copied into the
151         dictionary."""
152
153         dict = {}
154         if kw.has_key('env'):
155             dict.update(kw['env'])
156             del kw['env']
157
158         try:
159             cwd = kw['dir']
160         except:
161             cwd = None
162         else:
163             del kw['dir']
164
165         if kw.has_key('target'):
166             t = kw['target']
167             del kw['target']
168             if not SCons.Util.is_List(t):
169                 t = [t]
170             try:
171                 cwd = t[0].cwd
172             except AttributeError:
173                 pass
174             dict['TARGETS'] = SCons.Util.PathList(map(os.path.normpath, map(str, t)))
175             if dict['TARGETS']:
176                 dict['TARGET'] = dict['TARGETS'][0]
177
178         if kw.has_key('source'):
179             s = kw['source']
180             del kw['source']
181             if not SCons.Util.is_List(s):
182                 s = [s]
183             dict['SOURCES'] = SCons.Util.PathList(map(os.path.normpath, map(str, s)))
184
185         dict.update(kw)
186
187         # Autogenerate necessary construction variables.
188         SCons.Util.autogenerate(dict, dir = cwd)
189
190         return dict
191
192 class CommandAction(ActionBase):
193     """Class for command-execution actions."""
194     def __init__(self, string):
195         self.command = string
196
197     def execute(self, **kw):
198         import SCons.Util
199         dict = apply(self.subst_dict, (), kw)
200         cmd_list = SCons.Util.scons_subst_list(self.command, dict, {})
201         for cmd_line in cmd_list:
202             if len(cmd_line):
203                 if print_actions:
204                     self.show(string.join(cmd_line))
205                 if execute_actions:
206                     try:
207                         ENV = kw['env']['ENV']
208                     except:
209                         import SCons.Defaults
210                         ENV = SCons.Defaults.ConstructionEnvironment['ENV']
211                     ret = spawn(cmd_line[0], cmd_line, ENV)
212                     if ret:
213                         return ret
214         return 0
215
216     def get_contents(self, **kw):
217         """Return the signature contents of this action's command line.
218
219         For signature purposes, it doesn't matter what targets or
220         sources we use, so long as we use the same ones every time
221         so the signature stays the same.  We supply an array of two
222         of each to allow for distinction between TARGET and TARGETS.
223         """
224         kw['target'] = ['__t1__', '__t2__']
225         kw['source'] = ['__s1__', '__s2__']
226         dict = apply(self.subst_dict, (), kw)
227         return SCons.Util.scons_subst(self.command, dict, {})
228
229 class FunctionAction(ActionBase):
230     """Class for Python function actions."""
231     def __init__(self, function):
232         self.function = function
233
234     def execute(self, **kw):
235         # if print_actions:
236         # XXX:  WHAT SHOULD WE PRINT HERE?
237         if execute_actions:
238             if kw.has_key('target'):
239                 if SCons.Util.is_List(kw['target']):
240                     kw['target'] = map(str, kw['target'])
241                 else:
242                     kw['target'] = str(kw['target'])
243             if kw.has_key('source'):
244                 kw['source'] = map(str, kw['source'])
245             return apply(self.function, (), kw)
246
247     def get_contents(self, **kw):
248         """Return the signature contents of this callable action.
249
250         By providing direct access to the code object of the
251         function, Python makes this extremely easy.  Hooray!
252         """
253         #XXX DOES NOT ACCOUNT FOR CHANGES IN ENVIRONMENT VARIABLES
254         #THE FUNCTION MAY USE
255         try:
256             # "self.function" is a function.
257             code = self.function.func_code.co_code
258         except:
259             # "self.function" is a callable object.
260             code = self.function.__call__.im_func.func_code.co_code
261         return str(code)
262
263 class ListAction(ActionBase):
264     """Class for lists of other actions."""
265     def __init__(self, list):
266         self.list = map(lambda x: Action(x), list)
267
268     def execute(self, **kw):
269         for l in self.list:
270             r = apply(l.execute, (), kw)
271             if r != 0:
272                 return r
273         return 0
274
275     def get_contents(self, **kw):
276         """Return the signature contents of this action list.
277
278         Simple concatenation of the signatures of the elements.
279         """
280
281         return reduce(lambda x, y: x + str(y.get_contents()), self.list, "")