Several bug fixes from Charles Crain.
[scons.git] / src / engine / SCons / Builder.py
1 """SCons.Builder
2
3 XXX
4
5 """
6
7 #
8 # Copyright (c) 2001, 2002 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.path
35 import string
36 import copy
37 from SCons.Errors import UserError
38
39 import SCons.Action
40 import SCons.Node
41 import SCons.Node.FS
42 import SCons.Util
43
44 class DictCmdGenerator:
45     """This is a callable class that can be used as a
46     command generator function.  It holds on to a dictionary
47     mapping file suffixes to Actions.  It uses that dictionary
48     to return the proper action based on the file suffix of
49     the source file."""
50     
51     def __init__(self, action_dict):
52         self.action_dict = action_dict
53
54     def src_suffixes(self):
55         return self.action_dict.keys()
56
57     def __call__(self, source, target, env, **kw):
58         ext = None
59         for src in map(str, source):
60             my_ext = os.path.splitext(src)[1]
61             if ext and my_ext != ext:
62                 raise UserError("Cannot build multiple sources with different extensions.")
63             ext = my_ext
64
65         if ext is None:
66             raise UserError("Cannot deduce file extension from source files: %s" % repr(map(str, source)))
67         try:
68             # XXX Do we need to perform Environment substitution
69             # on the keys of action_dict before looking it up?
70             return self.action_dict[ext]
71         except KeyError:
72             raise UserError("Don't know how to build a file with suffix %s." % ext)
73
74 def Builder(**kw):
75     """A factory for builder objects."""
76     if kw.has_key('generator'):
77         if kw.has_key('action'):
78             raise UserError, "You must not specify both an action and a generator."
79         kw['action'] = SCons.Action.CommandGenerator(kw['generator'])
80         del kw['generator']
81     elif kw.has_key('action') and SCons.Util.is_Dict(kw['action']):
82         action_dict = kw['action']
83         kw['action'] = SCons.Action.CommandGenerator(DictCmdGenerator(action_dict))
84         kw['src_suffix'] = action_dict.keys()
85
86     if kw.has_key('emitter') and \
87        SCons.Util.is_String(kw['emitter']):
88         # This allows users to pass in an Environment
89         # variable reference (like "$FOO") as an emitter.
90         # We will look in that Environment variable for
91         # a callable to use as the actual emitter.
92         var = SCons.Util.get_environment_var(kw['emitter'])
93         if not var:
94             raise UserError, "Supplied emitter '%s' does not appear to refer to an Environment variable" % kw['emitter']
95         kw['emitter'] = EmitterProxy(var)
96         
97     if kw.has_key('src_builder'):
98         return apply(MultiStepBuilder, (), kw)
99     else:
100         return apply(BuilderBase, (), kw)
101
102 def _init_nodes(builder, env, tlist, slist):
103     """Initialize lists of target and source nodes with all of
104     the proper Builder information.
105     """
106     for s in slist:
107         src_key = s.scanner_key()        # the file suffix
108         scanner = env.get_scanner(src_key)
109         if scanner:
110             s.source_scanner = scanner
111             
112     for t in tlist:
113         t.cwd = SCons.Node.FS.default_fs.getcwd()       # XXX
114         t.builder_set(builder)
115         t.env_set(env)
116         t.add_source(slist)
117         if builder.scanner:
118             t.target_scanner = builder.scanner
119         
120 class _callable_adaptor:
121     """When crteating a Builder, you can pass a string OR
122     a callable in for prefix, suffix, or src_suffix.
123     src_suffix even takes a list!
124
125     If a string or list is passed, we use this class to
126     adapt it to a callable."""
127     def __init__(self, static):
128         self.static = static
129
130     def __call__(self, **kw):
131         return self.static
132
133     def __cmp__(self, other):
134         if isinstance(other, _callable_adaptor):
135             return cmp(self.static, other.static)
136         return -1
137
138 def _adjust_suffix(suff):
139     if suff and not suff[0] in [ '.', '$' ]:
140         return '.' + suff
141     return suff
142
143 class EmitterProxy:
144     """This is a callable class that can act as a
145     Builder emitter.  It holds on to a string that
146     is a key into an Environment dictionary, and will
147     look there at actual build time to see if it holds
148     a callable.  If so, we will call that as the actual
149     emitter."""
150     def __init__(self, var):
151         self.var = SCons.Util.to_String(var)
152
153     def __call__(self, target, source, env, **kw):
154         emitter = self.var
155
156         # Recursively substitue the variable.
157         # We can't use env.subst() because it deals only
158         # in strings.  Maybe we should change that?
159         while SCons.Util.is_String(emitter) and \
160               env.has_key(emitter):
161             emitter = env[emitter]
162         if not callable(emitter):
163             return (target, source)
164         args = { 'target':target,
165                  'source':source,
166                  'env':env }
167         args.update(kw)
168         return apply(emitter, (), args)
169
170 class BuilderBase:
171     """Base class for Builders, objects that create output
172     nodes (files) from input nodes (files).
173     """
174
175     def __init__(self,  name = None,
176                         action = None,
177                         prefix = '',
178                         suffix = '',
179                         src_suffix = '',
180                         node_factory = SCons.Node.FS.default_fs.File,
181                         target_factory = None,
182                         source_factory = None,
183                         scanner = None,
184                         emitter = None):
185         if name is None:
186             raise UserError, "You must specify a name for the builder."
187         self.name = name
188         self.action = SCons.Action.Action(action)
189
190         if callable(prefix):
191             self.prefix = prefix
192         else:
193             self.prefix = _callable_adaptor(str(prefix))
194
195         if callable(suffix):
196             self.suffix = suffix
197         else:
198             self.suffix = _callable_adaptor(str(suffix))
199
200         if callable(src_suffix):
201             self.src_suffix = src_suffix
202         elif SCons.Util.is_String(src_suffix):
203             self.src_suffix = _callable_adaptor([ str(src_suffix) ])
204         else:
205             self.src_suffix = _callable_adaptor(src_suffix)
206             
207         self.target_factory = target_factory or node_factory
208         self.source_factory = source_factory or node_factory
209         self.scanner = scanner
210
211         self.emitter = emitter
212
213     def __cmp__(self, other):
214         return cmp(self.__dict__, other.__dict__)
215
216     def _create_nodes(self, env, args, target = None, source = None):
217         """Create and return lists of target and source nodes.
218         """
219         def adjustixes(files, pre, suf):
220             ret = []
221             files = SCons.Util.argmunge(files)
222
223             for f in files:
224                 if SCons.Util.is_String(f):
225                     if pre and f[:len(pre)] != pre:
226                         path, fn = os.path.split(os.path.normpath(f))
227                         f = os.path.join(path, pre + fn)
228                     # Only append a suffix if the file does not have one.
229                     if suf and not os.path.splitext(f)[1]:
230                         if f[-len(suf):] != suf:
231                             f = f + suf
232                 ret.append(f)
233             return ret
234
235         pre = self.get_prefix(env, args)
236         suf = self.get_suffix(env, args)
237         tlist = SCons.Node.arg2nodes(adjustixes(target,
238                                                 pre, suf),
239                                      self.target_factory)
240         src_suf = self.get_src_suffix(env, args)
241         slist = SCons.Node.arg2nodes(adjustixes(source,
242                                                 None,
243                                                 src_suf),
244                                      self.source_factory)
245         if self.emitter:
246             emit_args = { 'target' : tlist,
247                           'source' : slist,
248                           'env' : env }
249             emit_args.update(args)
250             target, source = apply(self.emitter, (), emit_args)
251
252             # Have to run it through again in case the
253             # function returns non-Node targets/sources.
254             tlist = SCons.Node.arg2nodes(adjustixes(target,
255                                                     pre, suf),
256                                          self.target_factory)
257             slist = SCons.Node.arg2nodes(adjustixes(source,
258                                                     None,
259                                                     src_suf),
260                                          self.source_factory)
261             
262         for t in tlist:
263             t.build_args = args
264         return tlist, slist
265
266     def __call__(self, env, target = None, source = None, **kw):
267         tlist, slist = self._create_nodes(env, kw, target, source)
268
269         if len(tlist) == 1:
270             _init_nodes(self, env, tlist, slist)
271             tlist = tlist[0]
272         else:
273             _init_nodes(ListBuilder(self, env, tlist), env, tlist, slist)
274
275         return tlist
276
277
278     def execute(self, **kw):
279         """Execute a builder's action to create an output object.
280         """
281         return apply(self.action.execute, (), kw)
282
283     def get_raw_contents(self, **kw):
284         """Fetch the "contents" of the builder's action.
285         """
286         return apply(self.action.get_raw_contents, (), kw)
287
288     def get_contents(self, **kw):
289         """Fetch the "contents" of the builder's action
290         (for signature calculation).
291         """
292         return apply(self.action.get_contents, (), kw)
293
294     def src_suffixes(self, env, args):
295         return map(lambda x, e=env: e.subst(_adjust_suffix(x)),
296                    apply(self.src_suffix, (), args))
297
298     def get_src_suffix(self, env, args):
299         """Get the first src_suffix in the list of src_suffixes."""
300         ret = self.src_suffixes(env, args)
301         if not ret:
302             return ''
303         else:
304             return ret[0]
305
306     def get_suffix(self, env, args):
307         return env.subst(_adjust_suffix(apply(self.suffix, (), args)))
308
309     def get_prefix(self, env, args):
310         return env.subst(apply(self.prefix, (), args))
311
312     def targets(self, node):
313         """Return the list of targets for this builder instance.
314
315         For most normal builders, this is just the supplied node.
316         """
317         return [ node ]
318
319 class ListBuilder:
320     """This is technically not a Builder object, but a wrapper
321     around another Builder object.  This is designed to look
322     like a Builder object, though, for purposes of building an
323     array of targets from a single Action execution.
324     """
325
326     def __init__(self, builder, env, tlist):
327         self.builder = builder
328         self.scanner = builder.scanner
329         self.env = env
330         self.tlist = tlist
331
332     def execute(self, **kw):
333         if hasattr(self, 'status'):
334             return self.status
335         for t in self.tlist:
336             # unlink all targets and make all directories
337             # before building anything
338             t.prepare()
339         kw['target'] = self.tlist
340         self.status = apply(self.builder.execute, (), kw)
341         for t in self.tlist:
342             if not t is kw['target']:
343                 t.build()
344         return self.status
345
346     def get_raw_contents(self, **kw):
347         return apply(self.builder.get_raw_contents, (), kw)
348
349     def get_contents(self, **kw):
350         return apply(self.builder.get_contents, (), kw)
351
352     def src_suffixes(self, env, args):
353         return self.builder.src_suffixes(env, args)
354
355     def targets(self, node):
356         """Return the list of targets for this builder instance.
357         """
358         return self.tlist
359
360 class MultiStepBuilder(BuilderBase):
361     """This is a builder subclass that can build targets in
362     multiple steps.  The src_builder parameter to the constructor
363     accepts a builder that is called to build sources supplied to
364     this builder.  The targets of that first build then become
365     the sources of this builder.
366
367     If this builder has a src_suffix supplied, then the src_builder
368     builder is NOT invoked if the suffix of a source file matches
369     src_suffix.
370     """
371     def __init__(self,  src_builder,
372                         name = None,
373                         action = None,
374                         prefix = '',
375                         suffix = '',
376                         src_suffix = '',
377                         node_factory = SCons.Node.FS.default_fs.File,
378                         target_factory = None,
379                         source_factory = None,
380                         scanner=None,
381                         emitter=None):
382         BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
383                              node_factory, target_factory, source_factory,
384                              scanner, emitter)
385         if not SCons.Util.is_List(src_builder):
386             src_builder = [ src_builder ]
387         self.src_builder = src_builder
388         self.sdict = {}
389
390     def __call__(self, env, target = None, source = None, **kw):
391         slist = SCons.Node.arg2nodes(source, self.source_factory)
392         final_sources = []
393
394         r=repr(env)
395         try:
396             sdict = self.sdict[r]
397         except KeyError:
398             sdict = {}
399             self.sdict[r] = sdict
400             for bld in self.src_builder:
401                 for suf in bld.src_suffixes(env, kw):
402                     sdict[suf] = bld
403                     
404         for snode in slist:
405             path, ext = os.path.splitext(snode.abspath)
406             if sdict.has_key(ext):
407                 src_bld = sdict[ext]
408
409                 dictArgs = copy.copy(kw)
410                 dictArgs['target'] = [path]
411                 dictArgs['source'] = snode
412                 dictArgs['env'] = env
413                 tgt = apply(src_bld, (), dictArgs)
414                 if not SCons.Util.is_List(tgt):
415                     tgt = [ tgt ]
416
417                 # Only supply the builder with sources it is capable
418                 # of building.
419                 tgt = filter(lambda x,
420                              suf=self.src_suffixes(env, kw):
421                              os.path.splitext(SCons.Util.to_String(x))[1] in \
422                              suf, tgt)
423                 final_sources.extend(tgt)
424             else:
425                 final_sources.append(snode)
426         dictKwArgs = kw
427         dictKwArgs['target'] = target
428         dictKwArgs['source'] = final_sources
429         return apply(BuilderBase.__call__,
430                      (self, env), dictKwArgs)
431
432     def src_suffixes(self, env, args):
433         return BuilderBase.src_suffixes(self, env, args) + \
434                reduce(lambda x, y: x + y,
435                       map(lambda b, e=env, args=args: b.src_suffixes(e, args),
436                           self.src_builder),
437                       [])