Fix a couple obscure bugs. (Anthony Roach)
[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, args, tlist, slist):
103     """Initialize lists of target and source nodes with all of
104     the proper Builder information.
105     """
106
107     for s in slist:
108         src_key = s.scanner_key()        # the file suffix
109         scanner = env.get_scanner(src_key)
110         if scanner:
111             s.source_scanner = scanner
112
113     for t in tlist:
114         if t.builder is not None:
115             if t.env != env: 
116                 raise UserError, "Two different environments were specified for the same target: %s"%str(t)
117             elif t.build_args != args:
118                 raise UserError, "Two different sets of build arguments were specified for the same target: %s"%str(t)
119             elif builder.scanner and builder.scanner != t.target_scanner:
120                 raise UserError, "Two different scanners were specified for the same target: %s"%str(t)
121
122             if builder.multi:
123                 if t.builder != builder:
124                     if isinstance(t.builder, ListBuilder) and isinstance(builder, ListBuilder) and t.builder.builder == builder.builder:
125                         raise UserError, "Two different target sets have a target in common: %s"%str(t)
126                     else:
127                         raise UserError, "Two different builders (%s and %s) were specified for the same target: %s"%(t.builder.name, builder.name, str(t))
128             elif t.sources != slist:
129                 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
130
131         t.build_args = args
132         t.cwd = SCons.Node.FS.default_fs.getcwd()
133         t.builder_set(builder)
134         t.env_set(env)
135         t.add_source(slist)
136         if builder.scanner:
137             t.target_scanner = builder.scanner
138         
139 class _callable_adaptor:
140     """When crteating a Builder, you can pass a string OR
141     a callable in for prefix, suffix, or src_suffix.
142     src_suffix even takes a list!
143
144     If a string or list is passed, we use this class to
145     adapt it to a callable."""
146     def __init__(self, static):
147         self.static = static
148
149     def __call__(self, **kw):
150         return self.static
151
152     def __cmp__(self, other):
153         if isinstance(other, _callable_adaptor):
154             return cmp(self.static, other.static)
155         return -1
156
157 def _adjust_suffix(suff):
158     if suff and not suff[0] in [ '.', '$' ]:
159         return '.' + suff
160     return suff
161
162 class EmitterProxy:
163     """This is a callable class that can act as a
164     Builder emitter.  It holds on to a string that
165     is a key into an Environment dictionary, and will
166     look there at actual build time to see if it holds
167     a callable.  If so, we will call that as the actual
168     emitter."""
169     def __init__(self, var):
170         self.var = SCons.Util.to_String(var)
171
172     def __call__(self, target, source, env, **kw):
173         emitter = self.var
174
175         # Recursively substitue the variable.
176         # We can't use env.subst() because it deals only
177         # in strings.  Maybe we should change that?
178         while SCons.Util.is_String(emitter) and \
179               env.has_key(emitter):
180             emitter = env[emitter]
181         if not callable(emitter):
182             return (target, source)
183         args = { 'target':target,
184                  'source':source,
185                  'env':env }
186         args.update(kw)
187         return apply(emitter, (), args)
188
189 class BuilderBase:
190     """Base class for Builders, objects that create output
191     nodes (files) from input nodes (files).
192     """
193
194     def __init__(self,  name = None,
195                         action = None,
196                         prefix = '',
197                         suffix = '',
198                         src_suffix = '',
199                         node_factory = SCons.Node.FS.default_fs.File,
200                         target_factory = None,
201                         source_factory = None,
202                         scanner = None,
203                         emitter = None,
204                         multi = 0):
205         if name is None:
206             raise UserError, "You must specify a name for the builder."
207         self.name = name
208         self.action = SCons.Action.Action(action)
209         self.multi = multi
210
211         if callable(prefix):
212             self.prefix = prefix
213         else:
214             self.prefix = _callable_adaptor(str(prefix))
215
216         if callable(suffix):
217             self.suffix = suffix
218         else:
219             self.suffix = _callable_adaptor(str(suffix))
220
221         if callable(src_suffix):
222             self.src_suffix = src_suffix
223         elif SCons.Util.is_String(src_suffix):
224             self.src_suffix = _callable_adaptor([ str(src_suffix) ])
225         else:
226             self.src_suffix = _callable_adaptor(src_suffix)
227             
228         self.target_factory = target_factory or node_factory
229         self.source_factory = source_factory or node_factory
230         self.scanner = scanner
231
232         self.emitter = emitter
233
234     def __cmp__(self, other):
235         return cmp(self.__dict__, other.__dict__)
236
237     def _create_nodes(self, env, args, target = None, source = None):
238         """Create and return lists of target and source nodes.
239         """
240         def adjustixes(files, pre, suf):
241             ret = []
242             # FOR RELEASE 0.08:
243             #if not SCons.Util.is_List(files):
244             #    files = [files]
245             files = SCons.Util.argmunge(files)
246
247             for f in files:
248                 if SCons.Util.is_String(f):
249                     if pre and f[:len(pre)] != pre:
250                         path, fn = os.path.split(os.path.normpath(f))
251                         f = os.path.join(path, pre + fn)
252                     # Only append a suffix if the file does not have one.
253                     if suf and not os.path.splitext(f)[1]:
254                         if f[-len(suf):] != suf:
255                             f = f + suf
256                 ret.append(f)
257             return ret
258
259         pre = self.get_prefix(env, args)
260         suf = self.get_suffix(env, args)
261         src_suf = self.get_src_suffix(env, args)
262         if self.emitter:
263             # pass the targets and sources to the emitter as strings
264             # rather than nodes since str(node) doesn't work 
265             # properly from any directory other than the top directory,
266             # and emitters are called "in" the SConscript directory:
267             tlist = adjustixes(target, pre, suf)
268             slist = adjustixes(source, None, src_suf)
269
270             emit_args = { 'target' : tlist,
271                           'source' : slist,
272                           'env' : env }
273             emit_args.update(args)
274             target, source = apply(self.emitter, (), emit_args)
275
276         tlist = SCons.Node.arg2nodes(adjustixes(target, pre, suf),
277                                      self.target_factory)
278         slist = SCons.Node.arg2nodes(adjustixes(source, None, src_suf),
279                                      self.source_factory)
280
281         return tlist, slist
282
283     def __call__(self, env, target = None, source = None, **kw):
284         tlist, slist = self._create_nodes(env, kw, target, source)
285
286         if len(tlist) == 1:
287             _init_nodes(self, env, kw, tlist, slist)
288             tlist = tlist[0]
289         else:
290             _init_nodes(ListBuilder(self, env, tlist), env, kw, tlist, slist)
291
292         return tlist
293
294
295     def execute(self, **kw):
296         """Execute a builder's action to create an output object.
297         """
298         return apply(self.action.execute, (), kw)
299
300     def get_raw_contents(self, **kw):
301         """Fetch the "contents" of the builder's action.
302         """
303         return apply(self.action.get_raw_contents, (), kw)
304
305     def get_contents(self, **kw):
306         """Fetch the "contents" of the builder's action
307         (for signature calculation).
308         """
309         return apply(self.action.get_contents, (), kw)
310
311     def src_suffixes(self, env, args):
312         return map(lambda x, e=env: e.subst(_adjust_suffix(x)),
313                    apply(self.src_suffix, (), args))
314
315     def get_src_suffix(self, env, args):
316         """Get the first src_suffix in the list of src_suffixes."""
317         ret = self.src_suffixes(env, args)
318         if not ret:
319             return ''
320         else:
321             return ret[0]
322
323     def get_suffix(self, env, args):
324         return env.subst(_adjust_suffix(apply(self.suffix, (), args)))
325
326     def get_prefix(self, env, args):
327         return env.subst(apply(self.prefix, (), args))
328
329     def targets(self, node):
330         """Return the list of targets for this builder instance.
331
332         For most normal builders, this is just the supplied node.
333         """
334         return [ node ]
335
336 class ListBuilder:
337     """This is technically not a Builder object, but a wrapper
338     around another Builder object.  This is designed to look
339     like a Builder object, though, for purposes of building an
340     array of targets from a single Action execution.
341     """
342
343     def __init__(self, builder, env, tlist):
344         self.builder = builder
345         self.scanner = builder.scanner
346         self.env = env
347         self.tlist = tlist
348         self.multi = builder.multi
349         self.name = "ListBuilder(%s)"%builder.name
350
351     def execute(self, **kw):
352         if hasattr(self, 'status'):
353             return self.status
354         for t in self.tlist:
355             # unlink all targets and make all directories
356             # before building anything
357             t.prepare()
358         kw['target'] = self.tlist
359         self.status = apply(self.builder.execute, (), kw)
360         for t in self.tlist:
361             if not t is kw['target']:
362                 t.build()
363         return self.status
364
365     def get_raw_contents(self, **kw):
366         return apply(self.builder.get_raw_contents, (), kw)
367
368     def get_contents(self, **kw):
369         return apply(self.builder.get_contents, (), kw)
370
371     def src_suffixes(self, env, args):
372         return self.builder.src_suffixes(env, args)
373
374     def targets(self, node):
375         """Return the list of targets for this builder instance.
376         """
377         return self.tlist
378
379     def __cmp__(self, other):
380         return cmp(self.__dict__, other.__dict__)
381
382 class MultiStepBuilder(BuilderBase):
383     """This is a builder subclass that can build targets in
384     multiple steps.  The src_builder parameter to the constructor
385     accepts a builder that is called to build sources supplied to
386     this builder.  The targets of that first build then become
387     the sources of this builder.
388
389     If this builder has a src_suffix supplied, then the src_builder
390     builder is NOT invoked if the suffix of a source file matches
391     src_suffix.
392     """
393     def __init__(self,  src_builder,
394                         name = None,
395                         action = None,
396                         prefix = '',
397                         suffix = '',
398                         src_suffix = '',
399                         node_factory = SCons.Node.FS.default_fs.File,
400                         target_factory = None,
401                         source_factory = None,
402                         scanner=None,
403                         emitter=None):
404         BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
405                              node_factory, target_factory, source_factory,
406                              scanner, emitter)
407         if not SCons.Util.is_List(src_builder):
408             src_builder = [ src_builder ]
409         self.src_builder = src_builder
410         self.sdict = {}
411
412     def __call__(self, env, target = None, source = None, **kw):
413         slist = SCons.Node.arg2nodes(source, self.source_factory)
414         final_sources = []
415
416         r=repr(env)
417         try:
418             sdict = self.sdict[r]
419         except KeyError:
420             sdict = {}
421             self.sdict[r] = sdict
422             for bld in self.src_builder:
423                 for suf in bld.src_suffixes(env, kw):
424                     sdict[suf] = bld
425                     
426         for snode in slist:
427             path, ext = os.path.splitext(snode.abspath)
428             if sdict.has_key(ext):
429                 src_bld = sdict[ext]
430
431                 dictArgs = copy.copy(kw)
432                 dictArgs['target'] = [path]
433                 dictArgs['source'] = snode
434                 dictArgs['env'] = env
435                 tgt = apply(src_bld, (), dictArgs)
436                 if not SCons.Util.is_List(tgt):
437                     tgt = [ tgt ]
438
439                 # Only supply the builder with sources it is capable
440                 # of building.
441                 tgt = filter(lambda x,
442                              suf=self.src_suffixes(env, kw):
443                              os.path.splitext(SCons.Util.to_String(x))[1] in \
444                              suf, tgt)
445                 final_sources.extend(tgt)
446             else:
447                 final_sources.append(snode)
448         dictKwArgs = kw
449         dictKwArgs['target'] = target
450         dictKwArgs['source'] = final_sources
451         return apply(BuilderBase.__call__,
452                      (self, env), dictKwArgs)
453
454     def src_suffixes(self, env, args):
455         return BuilderBase.src_suffixes(self, env, args) + \
456                reduce(lambda x, y: x + y,
457                       map(lambda b, e=env, args=args: b.src_suffixes(e, args),
458                           self.src_builder),
459                       [])