Make the (s) and variables upper-case.
[scons.git] / src / engine / SCons / Builder.py
1 """SCons.Builder
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
33
34 import os
35 import os.path
36 import SCons.Node.FS
37 from SCons.Util import PathList, scons_str2nodes, scons_subst
38 import string
39 import types
40 from UserList import UserList
41 from UserDict import UserDict
42 from Errors import UserError
43
44 try:
45     from UserString import UserString
46 except ImportError:
47     class UserString:
48         pass
49
50
51
52 if os.name == 'posix':
53
54     def spawn(cmd, args, env):
55         pid = os.fork()
56         if not pid:
57             # Child process.
58             os.execvpe(cmd, args, env)
59         else:
60             # Parent process.
61             pid, stat = os.waitpid(pid, 0)
62             ret = stat >> 8
63             return ret
64
65 elif os.name == 'nt':
66
67     def pathsearch(cmd, env):
68         # In order to deal with the fact that 1.5.2 doesn't have
69         # os.spawnvpe(), roll our own PATH search.
70         if os.path.isabs(cmd):
71             if not os.path.exists(cmd):
72                 exts = env['PATHEXT']
73                 if type(exts) != type([]):
74                     exts = string.split(exts, os.pathsep)
75                 for e in exts:
76                     f = cmd + e
77                     if os.path.exists(f):
78                         return f
79         else:
80             path = env['PATH']
81             if type(path) != type([]):
82                 path = string.split(path, os.pathsep)
83             exts = env['PATHEXT']
84             if type(exts) != type([]):
85                 exts = string.split(exts, os.pathsep)
86             pairs = []
87             for dir in path:
88                 for e in [None] + exts:
89                     pairs.append(dir, e)
90             for dir, ext in pairs:
91                 f = os.path.join(dir, cmd)
92                 if not ext is None:
93                     f = f + ext
94                 if os.path.exists(f):
95                     return f
96         return cmd
97
98     def spawn(cmd, args, env):
99         try:
100             ret = os.spawnvpe(os.P_WAIT, cmd, args, env)
101         except AttributeError:
102             cmd = pathsearch(cmd, env)
103             ret = os.spawnve(os.P_WAIT, cmd, args, env)
104         return ret
105
106
107
108 def Builder(**kw):
109     """A factory for builder objects."""
110     if kw.has_key('src_builder'):
111         return apply(MultiStepBuilder, (), kw)
112     elif kw.has_key('action') and (type(kw['action']) is types.DictType or
113                                    isinstance(kw['action'], UserDict)):
114         action_dict = kw['action']
115         builders = []
116         for suffix, action in action_dict.items():
117             bld_kw = kw.copy()
118             bld_kw['action'] = action
119             bld_kw['src_suffix'] = suffix
120             builders.append(apply(BuilderBase, (), bld_kw))
121         del kw['action']
122         kw['builders'] = builders
123         return apply(CompositeBuilder, (), kw)
124     else:
125         return apply(BuilderBase, (), kw)
126
127
128
129 class BuilderBase:
130     """Base class for Builders, objects that create output
131     nodes (files) from input nodes (files).
132     """
133
134     def __init__(self,  name = None,
135                         action = None,
136                         prefix = '',
137                         suffix = '',
138                         src_suffix = '',
139                         node_factory = SCons.Node.FS.default_fs.File):
140         self.name = name
141         self.action = Action(action)
142
143         self.prefix = prefix
144         self.suffix = suffix
145         self.src_suffix = src_suffix
146         self.node_factory = node_factory
147         if self.suffix and self.suffix[0] not in '.$':
148             self.suffix = '.' + self.suffix
149         if self.src_suffix and self.src_suffix[0] not in '.$':
150             self.src_suffix = '.' + self.src_suffix
151
152     def __cmp__(self, other):
153         return cmp(self.__dict__, other.__dict__)
154
155     def __call__(self, env, target = None, source = None):
156         def adjustixes(files, pre, suf):
157             ret = []
158             if not type(files) is type([]):
159                 files = [files]
160             for f in files:
161                 if type(f) == type(""):
162                     if pre and f[:len(pre)] != pre:
163                         f = pre + f
164                     if suf:
165                         if f[-len(suf):] != suf:
166                             f = f + suf
167                 ret.append(f)
168             return ret
169
170         tlist = scons_str2nodes(adjustixes(target,
171                                            env.subst(self.prefix),
172                                            env.subst(self.suffix)),
173                                 self.node_factory)
174         slist = scons_str2nodes(adjustixes(source, None,
175                                            env.subst(self.src_suffix)),
176                                 self.node_factory)
177         for t in tlist:
178             t.builder_set(self)
179             t.env_set(env)
180             t.add_source(slist)
181
182         if len(tlist) == 1:
183             tlist = tlist[0]
184         return tlist
185
186     def execute(self, **kw):
187         """Execute a builder's action to create an output object.
188         """
189         return apply(self.action.execute, (), kw)
190
191 class MultiStepBuilder(BuilderBase):
192     """This is a builder subclass that can build targets in
193     multiple steps.  The src_builder parameter to the constructor
194     accepts a builder that is called to build sources supplied to
195     this builder.  The targets of that first build then become
196     the sources of this builder.
197
198     If this builder has a src_suffix supplied, then the src_builder
199     builder is NOT invoked if the suffix of a source file matches
200     src_suffix.
201     """
202     def __init__(self,  src_builder,
203                         name = None,
204                         action = None,
205                         prefix = '',
206                         suffix = '',
207                         src_suffix = '',
208                         node_factory = SCons.Node.FS.default_fs.File):
209         BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
210                              node_factory)
211         self.src_builder = src_builder
212
213     def __call__(self, env, target = None, source = None):
214         slist = scons_str2nodes(source, self.node_factory)
215         final_sources = []
216         src_suffix = env.subst(self.src_suffix)
217         for snode in slist:
218             path, ext = os.path.splitext(snode.path)
219             if not src_suffix or ext != src_suffix:
220                 tgt = self.src_builder(env, target = [ path ],
221                                      source=snode)
222                 if not type(tgt) is types.ListType:
223                     final_sources.append(tgt)
224                 else:
225                     final_sources.extend(tgt)
226             else:
227                 final_sources.append(snode)
228         return BuilderBase.__call__(self, env, target=target,
229                                     source=final_sources)
230
231 class CompositeBuilder(BuilderBase):
232     """This is a convenient Builder subclass that can build different
233     files based on their suffixes.  For each target, this builder
234     will examine the target's sources.  If they are all the same
235     suffix, and that suffix is equal to one of the child builders'
236     src_suffix, then that child builder will be used.  Otherwise,
237     UserError is thrown.
238
239     Child builders are supplied via the builders arg to the
240     constructor.  Each must have its src_suffix set."""
241     def __init__(self,  name = None,
242                         prefix='',
243                         suffix='',
244                         builders=[]):
245         BuilderBase.__init__(self, name=name, prefix=prefix,
246                              suffix=suffix)
247         self.builder_dict = {}
248         for bld in builders:
249             if not bld.src_suffix:
250                 raise InternalError, "All builders supplied to CompositeBuilder class must have a src_suffix."
251             self.builder_dict[bld.src_suffix] = bld
252
253     def __call__(self, env, target = None, source = None):
254         ret = BuilderBase.__call__(self, env, target=target, source=source)
255
256         builder_dict = {}
257         for suffix, bld in self.builder_dict.items():
258             builder_dict[env.subst(bld.src_suffix)] = bld
259
260         if type(ret) is types.ListType:
261             tlist = ret
262         else:
263             tlist = [ ret ]
264         for tnode in tlist:
265             suflist = map(lambda x: os.path.splitext(x.path)[1],
266                           tnode.sources)
267             last_suffix=''
268             for suffix in suflist:
269                 if last_suffix and last_suffix != suffix:
270                     raise UserError, "The builder for %s is only capable of building source files of identical suffixes." % tnode.path
271                 last_suffix = suffix
272             if last_suffix:
273                 try:
274                     tnode.builder_set(builder_dict[last_suffix])
275                 except KeyError:
276                     raise UserError, "Builder not capable of building files with suffix: %s" % suffix
277         return ret
278
279 print_actions = 1;
280 execute_actions = 1;
281
282 def Action(act):
283     """A factory for action objects."""
284     if callable(act):
285         return FunctionAction(act)
286     elif type(act) == types.StringType or isinstance(act, UserString):
287         return CommandAction(act)
288     elif type(act) == types.ListType or isinstance(act, UserList):
289         return ListAction(act)
290     else:
291         return None
292     
293 class ActionBase:
294     """Base class for actions that create output objects.
295     
296     We currently expect Actions will only be accessible through
297     Builder objects, so they don't yet merit their own module."""
298     def __cmp__(self, other):
299         return cmp(self.__dict__, other.__dict__)
300
301     def show(self, string):
302         print string
303
304 class CommandAction(ActionBase):
305     """Class for command-execution actions."""
306     def __init__(self, string):
307         self.command = string
308
309     def execute(self, **kw):
310         loc = {}
311         if kw.has_key('target'):
312             t = kw['target']
313             if type(t) is type(""):
314                 t = [t]
315             loc['TARGETS'] = PathList(map(os.path.normpath, t))
316             loc['TARGET'] = loc['TARGETS'][0]
317         if kw.has_key('source'):
318             s = kw['source']
319             if type(s) is type(""):
320                 s = [s]
321             loc['SOURCES'] = PathList(map(os.path.normpath, s))
322
323         glob = {}
324         if kw.has_key('env'):
325             glob = kw['env']
326
327         cmd_str = scons_subst(self.command, loc, glob)
328         for cmd in string.split(cmd_str, '\n'):
329             if print_actions:
330                 self.show(cmd)
331             if execute_actions:
332                 args = string.split(cmd)
333                 try:
334                     ENV = glob['ENV']
335                 except:
336                     import SCons.Defaults
337                     ENV = SCons.Defaults.ConstructionEnvironment['ENV']
338                 ret = spawn(args[0], args, ENV)
339                 if ret:
340                     #XXX This doesn't account for ignoring errors (-i)
341                     return ret
342         return 0
343
344
345
346 class FunctionAction(ActionBase):
347     """Class for Python function actions."""
348     def __init__(self, function):
349         self.function = function
350
351     def execute(self, **kw):
352         # if print_actions:
353         # XXX:  WHAT SHOULD WE PRINT HERE?
354         if execute_actions:
355             return self.function(kw)
356
357 class ListAction(ActionBase):
358     """Class for lists of other actions."""
359     def __init__(self, list):
360         self.list = map(lambda x: Action(x), list)
361
362     def execute(self, **kw):
363         for l in self.list:
364             r = apply(l.execute, (), kw)
365             if r != 0:
366                 return r
367         return 0