Make sure auto-deducing target names works when a Node is passed in as a source file.
[scons.git] / src / engine / SCons / Builder.py
1 """SCons.Builder
2
3 Builder object subsystem.
4
5 A Builder object is a callable that encapsulates information about how
6 to execute actions to create a Node (file) from other Nodes (files), and
7 how to create those dependencies for tracking.
8
9 The main entry point here is the Builder() factory method.  This
10 provides a procedural interface that creates the right underlying
11 Builder object based on the keyword arguments supplied and the types of
12 the arguments.
13
14 The goal is for this external interface to be simple enough that the
15 vast majority of users can create new Builders as necessary to support
16 building new types of files in their configurations, without having to
17 dive any deeper into this subsystem.
18
19 """
20
21 #
22 # Copyright (c) 2001, 2002 Steven Knight
23 #
24 # Permission is hereby granted, free of charge, to any person obtaining
25 # a copy of this software and associated documentation files (the
26 # "Software"), to deal in the Software without restriction, including
27 # without limitation the rights to use, copy, modify, merge, publish,
28 # distribute, sublicense, and/or sell copies of the Software, and to
29 # permit persons to whom the Software is furnished to do so, subject to
30 # the following conditions:
31 #
32 # The above copyright notice and this permission notice shall be included
33 # in all copies or substantial portions of the Software.
34 #
35 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
36 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
37 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
38 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
39 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
40 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
41 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
42 #
43
44 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
45
46
47
48 import os.path
49 import string
50 import copy
51 from SCons.Errors import UserError
52
53 import SCons.Action
54 import SCons.Node
55 import SCons.Node.FS
56 import SCons.Util
57 import SCons.Warnings
58
59 class _Null:
60     pass
61
62 _null = _Null
63
64 class DictCmdGenerator:
65     """This is a callable class that can be used as a
66     command generator function.  It holds on to a dictionary
67     mapping file suffixes to Actions.  It uses that dictionary
68     to return the proper action based on the file suffix of
69     the source file."""
70     
71     def __init__(self, action_dict):
72         self.action_dict = action_dict
73
74     def src_suffixes(self):
75         return self.action_dict.keys()
76
77     def add_action(self, suffix, action):
78         """Add a suffix-action pair to the mapping.
79         """
80         self.action_dict[suffix] = action
81
82     def __call__(self, source, target, env, **kw):
83         ext = None
84         for src in map(str, source):
85             my_ext = SCons.Util.splitext(src)[1]
86             if ext and my_ext != ext:
87                 raise UserError("Cannot build multiple sources with different extensions: %s, %s" % (ext, my_ext))
88             ext = my_ext
89
90         if ext is None:
91             raise UserError("Cannot deduce file extension from source files: %s" % repr(map(str, source)))
92         try:
93             # XXX Do we need to perform Environment substitution
94             # on the keys of action_dict before looking it up?
95             return self.action_dict[ext]
96         except KeyError:
97             raise UserError("Don't know how to build a file with suffix %s." % ext)
98     def __cmp__(self, other):
99         return cmp(self.action_dict, other.action_dict)
100
101 def Builder(**kw):
102     """A factory for builder objects."""
103     composite = None
104     if kw.has_key('name'):
105         SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
106                             "The use of the 'name' parameter to Builder() is deprecated.")
107     if kw.has_key('generator'):
108         if kw.has_key('action'):
109             raise UserError, "You must not specify both an action and a generator."
110         kw['action'] = SCons.Action.CommandGenerator(kw['generator'])
111         del kw['generator']
112     elif kw.has_key('action') and SCons.Util.is_Dict(kw['action']):
113         composite = DictCmdGenerator(kw['action'])
114         kw['action'] = SCons.Action.CommandGenerator(composite)
115         kw['src_suffix'] = composite.src_suffixes()
116
117     if kw.has_key('emitter') and \
118        SCons.Util.is_String(kw['emitter']):
119         # This allows users to pass in an Environment
120         # variable reference (like "$FOO") as an emitter.
121         # We will look in that Environment variable for
122         # a callable to use as the actual emitter.
123         var = SCons.Util.get_environment_var(kw['emitter'])
124         if not var:
125             raise UserError, "Supplied emitter '%s' does not appear to refer to an Environment variable" % kw['emitter']
126         kw['emitter'] = EmitterProxy(var)
127         
128     if kw.has_key('src_builder'):
129         ret = apply(MultiStepBuilder, (), kw)
130     else:
131         ret = apply(BuilderBase, (), kw)
132
133     if composite:
134         ret = CompositeBuilder(ret, composite)
135
136     return ret
137
138 def _init_nodes(builder, env, args, tlist, slist):
139     """Initialize lists of target and source nodes with all of
140     the proper Builder information.
141     """
142
143     for s in slist:
144         src_key = s.scanner_key()        # the file suffix
145         scanner = env.get_scanner(src_key)
146         if scanner:
147             s.source_scanner = scanner
148
149     for t in tlist:
150         if t.side_effect:
151             raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
152         if t.builder is not None:
153             if t.env != env: 
154                 raise UserError, "Two different environments were specified for the same target: %s"%str(t)
155             elif t.build_args != args:
156                 raise UserError, "Two different sets of build arguments were specified for the same target: %s"%str(t)
157             elif builder.scanner and builder.scanner != t.target_scanner:
158                 raise UserError, "Two different scanners were specified for the same target: %s"%str(t)
159
160             if builder.multi:
161                 if t.builder != builder:
162                     if isinstance(t.builder, ListBuilder) and isinstance(builder, ListBuilder) and t.builder.builder == builder.builder:
163                         raise UserError, "Two different target sets have a target in common: %s"%str(t)
164                     else:
165                         raise UserError, "Two different builders (%s and %s) were specified for the same target: %s"%(t.builder.get_name(env), builder.get_name(env), str(t))
166             elif t.sources != slist:
167                 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
168
169         t.build_args = args
170         t.cwd = SCons.Node.FS.default_fs.getcwd()
171         t.builder_set(builder)
172         t.env_set(env)
173         t.add_source(slist)
174         if builder.scanner:
175             t.target_scanner = builder.scanner
176             
177 def _adjust_suffix(suff):
178     if suff and not suff[0] in [ '.', '$' ]:
179         return '.' + suff
180     return suff
181
182 class EmitterProxy:
183     """This is a callable class that can act as a
184     Builder emitter.  It holds on to a string that
185     is a key into an Environment dictionary, and will
186     look there at actual build time to see if it holds
187     a callable.  If so, we will call that as the actual
188     emitter."""
189     def __init__(self, var):
190         self.var = SCons.Util.to_String(var)
191
192     def __call__(self, target, source, env, **kw):
193         emitter = self.var
194
195         # Recursively substitute the variable.
196         # We can't use env.subst() because it deals only
197         # in strings.  Maybe we should change that?
198         while SCons.Util.is_String(emitter) and \
199               env.has_key(emitter):
200             emitter = env[emitter]
201         if not callable(emitter):
202             return (target, source)
203         args = { 'target':target,
204                  'source':source,
205                  'env':env }
206         args.update(kw)
207         return apply(emitter, (), args)
208
209     def __cmp__(self, other):
210         return cmp(self.var, other.var)
211
212 class BuilderBase:
213     """Base class for Builders, objects that create output
214     nodes (files) from input nodes (files).
215     """
216
217     def __init__(self,  name = None,
218                         action = None,
219                         prefix = '',
220                         suffix = '',
221                         src_suffix = '',
222                         node_factory = SCons.Node.FS.default_fs.File,
223                         target_factory = None,
224                         source_factory = None,
225                         scanner = None,
226                         emitter = None,
227                         multi = 0):
228         self.name = name
229         self.action = SCons.Action.Action(action)
230         self.multi = multi
231         self.prefix = prefix
232         self.suffix = suffix
233
234         self.set_src_suffix(src_suffix)
235
236         self.target_factory = target_factory or node_factory
237         self.source_factory = source_factory or node_factory
238         self.scanner = scanner
239
240         self.emitter = emitter
241
242     def get_name(self, env):
243         """Attempts to get the name of the Builder.
244
245         If the Builder's name attribute is None, then we will look at
246         the BUILDERS variable of env, expecting it to be a dictionary
247         containing this Builder, and we will return the key of the
248         dictionary."""
249
250         if self.name:
251             return self.name
252         try:
253             index = env['BUILDERS'].values().index(self)
254             return env['BUILDERS'].keys()[index]
255         except (AttributeError, KeyError, ValueError):
256             return str(self.__class__)
257
258     def __cmp__(self, other):
259         return cmp(self.__dict__, other.__dict__)
260
261     def _create_nodes(self, env, args, target = None, source = None):
262         """Create and return lists of target and source nodes.
263         """
264         def adjustixes(files, pre, suf):
265             if not files:
266                 return []
267             ret = []
268             if not SCons.Util.is_List(files):
269                 files = [files]
270
271             for f in files:
272                 if SCons.Util.is_String(f):
273                     if pre and f[:len(pre)] != pre:
274                         path, fn = os.path.split(os.path.normpath(f))
275                         f = os.path.join(path, pre + fn)
276                     # Only append a suffix if the file does not have one.
277                     if suf and not SCons.Util.splitext(f)[1]:
278                         if f[-len(suf):] != suf:
279                             f = f + suf
280                 ret.append(f)
281             return ret
282
283         pre = self.get_prefix(env)
284         suf = self.get_suffix(env)
285         src_suf = self.get_src_suffix(env)
286
287         source = adjustixes(source, None, src_suf)
288         if target is None:
289             target = map(lambda x, s=suf:
290                                 os.path.splitext(os.path.split(str(x))[1])[0] + s,
291                          source)
292         else:
293             target = adjustixes(target, pre, suf)
294
295         if self.emitter:
296             # pass the targets and sources to the emitter as strings
297             # rather than nodes since str(node) doesn't work 
298             # properly from any directory other than the top directory,
299             # and emitters are called "in" the SConscript directory:
300             emit_args = { 'target' : target,
301                           'source' : source,
302                           'env' : env }
303             emit_args.update(args)
304             target, source = apply(self.emitter, (), emit_args)
305
306         slist = SCons.Node.arg2nodes(source, self.source_factory)
307         tlist = SCons.Node.arg2nodes(target, self.target_factory)
308
309         return tlist, slist
310
311     def __call__(self, env, target = None, source = _null, **kw):
312         if source is _null:
313             source = target
314             target = None
315         tlist, slist = self._create_nodes(env, kw, target, source)
316
317         if len(tlist) == 1:
318             _init_nodes(self, env, kw, tlist, slist)
319             tlist = tlist[0]
320         else:
321             _init_nodes(ListBuilder(self, env, tlist), env, kw, tlist, slist)
322
323         return tlist
324
325     def execute(self, **kw):
326         """Execute a builder's action to create an output object.
327         """
328         return apply(self.action.execute, (), kw)
329
330     def get_raw_contents(self, **kw):
331         """Fetch the "contents" of the builder's action.
332         """
333         return apply(self.action.get_raw_contents, (), kw)
334
335     def get_contents(self, **kw):
336         """Fetch the "contents" of the builder's action
337         (for signature calculation).
338         """
339         return apply(self.action.get_contents, (), kw)
340
341     def src_suffixes(self, env):
342         return map(lambda x, e=env: e.subst(_adjust_suffix(x)),
343                    self.src_suffix)
344
345     def set_src_suffix(self, src_suffix):
346         if not src_suffix:
347             src_suffix = []
348         elif not SCons.Util.is_List(src_suffix):
349             src_suffix = [ src_suffix ]
350         self.src_suffix = src_suffix
351
352     def get_src_suffix(self, env):
353         """Get the first src_suffix in the list of src_suffixes."""
354         ret = self.src_suffixes(env)
355         if not ret:
356             return ''
357         return ret[0]
358
359     def get_suffix(self, env):
360         return env.subst(_adjust_suffix(self.suffix))
361
362     def get_prefix(self, env):
363         return env.subst(self.prefix)
364
365     def targets(self, node):
366         """Return the list of targets for this builder instance.
367
368         For most normal builders, this is just the supplied node.
369         """
370         return [ node ]
371
372 class ListBuilder(SCons.Util.Proxy):
373     """A Proxy to support building an array of targets (for example,
374     foo.o and foo.h from foo.y) from a single Action execution.
375     """
376
377     def __init__(self, builder, env, tlist):
378         SCons.Util.Proxy.__init__(self, builder)
379         self.builder = builder
380         self.scanner = builder.scanner
381         self.env = env
382         self.tlist = tlist
383         self.multi = builder.multi
384         self.name = "ListBuilder(%s)"%builder.name
385
386     def execute(self, **kw):
387         if hasattr(self, 'status'):
388             return self.status
389         for t in self.tlist:
390             # unlink all targets and make all directories
391             # before building anything
392             t.prepare()
393         kw['target'] = self.tlist
394         self.status = apply(self.builder.execute, (), kw)
395         for t in self.tlist:
396             if not t is kw['target']:
397                 t.build()
398         return self.status
399
400     def targets(self, node):
401         """Return the list of targets for this builder instance.
402         """
403         return self.tlist
404
405     def __cmp__(self, other):
406         return cmp(self.__dict__, other.__dict__)
407
408     def get_name(self, env):
409         """Attempts to get the name of the Builder.
410
411         If the Builder's name attribute is None, then we will look at
412         the BUILDERS variable of env, expecting it to be a dictionary
413         containing this Builder, and we will return the key of the
414         dictionary."""
415
416         return "ListBuilder(%s)" % self.builder.get_name(env)
417
418 class MultiStepBuilder(BuilderBase):
419     """This is a builder subclass that can build targets in
420     multiple steps.  The src_builder parameter to the constructor
421     accepts a builder that is called to build sources supplied to
422     this builder.  The targets of that first build then become
423     the sources of this builder.
424
425     If this builder has a src_suffix supplied, then the src_builder
426     builder is NOT invoked if the suffix of a source file matches
427     src_suffix.
428     """
429     def __init__(self,  src_builder,
430                         name = None,
431                         action = None,
432                         prefix = '',
433                         suffix = '',
434                         src_suffix = '',
435                         node_factory = SCons.Node.FS.default_fs.File,
436                         target_factory = None,
437                         source_factory = None,
438                         scanner=None,
439                         emitter=None):
440         BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
441                              node_factory, target_factory, source_factory,
442                              scanner, emitter)
443         if not SCons.Util.is_List(src_builder):
444             src_builder = [ src_builder ]
445         self.src_builder = src_builder
446         self.sdict = {} 
447         self.cached_src_suffixes = {} # source suffixes keyed on id(env)
448
449     def __call__(self, env, target = None, source = None, **kw):
450         slist = SCons.Node.arg2nodes(source, self.source_factory)
451         final_sources = []
452
453         try:
454             sdict = self.sdict[id(env)]
455         except KeyError:
456             sdict = {}
457             self.sdict[id(env)] = sdict
458             for bld in self.src_builder:
459                 if SCons.Util.is_String(bld):
460                     try:
461                         bld = env['BUILDERS'][bld]
462                     except KeyError:
463                         continue
464                 for suf in bld.src_suffixes(env):
465                     sdict[suf] = bld
466                     
467         src_suffixes = self.src_suffixes(env)
468         for snode in slist:
469             path, ext = SCons.Util.splitext(snode.abspath)
470             if sdict.has_key(ext):
471                 src_bld = sdict[ext]
472                 tgt = apply(src_bld, (env, path, snode), kw)
473                 # Only supply the builder with sources it is capable
474                 # of building.
475                 if SCons.Util.is_List(tgt):
476                     tgt = filter(lambda x, suf=src_suffixes:
477                                  SCons.Util.splitext(SCons.Util.to_String(x))[1] in suf,
478                                  tgt)
479                 if not SCons.Util.is_List(tgt):
480                     final_sources.append(tgt)
481                 else:
482                     final_sources.extend(tgt)
483             else:
484                 final_sources.append(snode)
485
486         return apply(BuilderBase.__call__,
487                      (self, env, target, final_sources), kw)
488
489     def get_src_builders(self, env):
490         """Return all the src_builders for this Builder.
491
492         This is essentially a recursive descent of the src_builder "tree."
493         """
494         ret = []
495         for bld in self.src_builder:
496             if SCons.Util.is_String(bld):
497                 # All Environments should have a BUILDERS
498                 # variable, so no need to check for it.
499                 try:
500                     bld = env['BUILDERS'][bld]
501                 except KeyError:
502                     continue
503             ret.append(bld)
504         return ret
505
506     def src_suffixes(self, env):
507         """Return a list of the src_suffix attributes for all
508         src_builders of this Builder.
509         """
510         try:
511             return self.cached_src_suffixes[id(env)]
512         except KeyError:
513             suffixes = BuilderBase.src_suffixes(self, env)
514             for builder in self.get_src_builders(env):
515                 suffixes.extend(builder.src_suffixes(env))
516             self.cached_src_suffixes[id(env)] = suffixes
517             return suffixes
518
519 class CompositeBuilder(SCons.Util.Proxy):
520     """A Builder Proxy whose main purpose is to always have
521     a DictCmdGenerator as its action, and to provide access
522     to the DictCmdGenerator's add_action() method.
523     """
524
525     def __init__(self, builder, cmdgen):
526         SCons.Util.Proxy.__init__(self, builder)
527
528         # cmdgen should always be an instance of DictCmdGenerator.
529         self.cmdgen = cmdgen
530         self.builder = builder
531
532     def add_action(self, suffix, action):
533         self.cmdgen.add_action(suffix, action)
534         self.set_src_suffix(self.cmdgen.src_suffixes())
535         
536     def __cmp__(self, other):
537         return cmp(self.__dict__, other.__dict__)