Allow prefixes and suffixes to be selected from dictionaries keyd by 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__
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 import os.path
47 import UserDict
48
49 import SCons.Action
50 from SCons.Errors import InternalError, UserError
51 import SCons.Executor
52 import SCons.Node
53 import SCons.Node.FS
54 import SCons.Util
55 import SCons.Warnings
56
57 class _Null:
58     pass
59
60 _null = _Null
61
62 class DictCmdGenerator:
63     """This is a callable class that can be used as a
64     command generator function.  It holds on to a dictionary
65     mapping file suffixes to Actions.  It uses that dictionary
66     to return the proper action based on the file suffix of
67     the source file."""
68     
69     def __init__(self, action_dict):
70         self.action_dict = action_dict
71
72     def src_suffixes(self):
73         return self.action_dict.keys()
74
75     def add_action(self, suffix, action):
76         """Add a suffix-action pair to the mapping.
77         """
78         self.action_dict[suffix] = action
79
80     def __call__(self, target, source, env, for_signature):
81         ext = None
82         for src in map(str, source):
83             my_ext = SCons.Util.splitext(src)[1]
84             if ext and my_ext != ext:
85                 raise UserError("While building `%s' from `%s': Cannot build multiple sources with different extensions: %s, %s" % (repr(map(str, target)), src, ext, my_ext))
86             ext = my_ext
87
88         if not ext:
89             raise UserError("While building `%s': Cannot deduce file extension from source files: %s" % (repr(map(str, target)), repr(map(str, source))))
90         try:
91             return self.action_dict[ext]
92         except KeyError:
93             # Before raising the user error, try to perform Environment
94             # substitution on the keys of action_dict.
95             s_dict = {}
96             for (k,v) in self.action_dict.items():
97                 s_k = env.subst(k)
98                 if s_dict.has_key(s_k):
99                     # XXX Note that we do only raise errors, when variables
100                     # point to the same suffix. If one suffix is a
101                     # literal and a variable suffix contains this literal
102                     # we don't raise an error (cause the literal 'wins')
103                     raise UserError("Ambiguous suffixes after environment substitution: %s == %s == %s" % (s_dict[s_k][0], k, s_k))
104                 s_dict[s_k] = (k,v)
105             try:
106                 return s_dict[ext][1]
107             except KeyError:
108                 raise UserError("While building `%s': Don't know how to build a file with suffix %s." % (repr(map(str, target)), repr(ext)))
109
110     def __cmp__(self, other):
111         return cmp(self.action_dict, other.action_dict)
112
113 class Selector(UserDict.UserDict):
114     """A callable dictionary that maps file suffixes to dictionary
115     values."""
116     def __call__(self, env, source):
117         ext = SCons.Util.splitext(str(source[0]))[1]
118         try:
119             return self[ext]
120         except KeyError:
121             # Try to perform Environment substitution on the keys of
122             # emitter_dict before giving up.
123             s_dict = {}
124             for (k,v) in self.items():
125                 s_k = env.subst(k)
126                 s_dict[s_k] = v
127             try:
128                 return s_dict[ext]
129             except KeyError:
130                 try:
131                     return self[None]
132                 except KeyError:
133                     return None
134
135 class DictEmitter(Selector):
136     """A callable dictionary that maps file suffixes to emitters.
137     When called, it finds the right emitter in its dictionary for the
138     suffix of the first source file, and calls that emitter to get the
139     right lists of targets and sources to return.  If there's no emitter
140     for the suffix in its dictionary, the original target and source are
141     returned.
142     """
143     def __call__(self, target, source, env):
144         emitter = Selector.__call__(self, env, source)
145         if emitter:
146             target, source = emitter(target, source, env)
147         return (target, source)
148
149 def Builder(**kw):
150     """A factory for builder objects."""
151     composite = None
152     if kw.has_key('generator'):
153         if kw.has_key('action'):
154             raise UserError, "You must not specify both an action and a generator."
155         kw['action'] = SCons.Action.CommandGenerator(kw['generator'])
156         del kw['generator']
157     elif kw.has_key('action') and SCons.Util.is_Dict(kw['action']):
158         composite = DictCmdGenerator(kw['action'])
159         kw['action'] = SCons.Action.CommandGenerator(composite)
160         kw['src_suffix'] = composite.src_suffixes()
161
162     if kw.has_key('emitter'):
163         emitter = kw['emitter']
164         if SCons.Util.is_String(emitter):
165             # This allows users to pass in an Environment
166             # variable reference (like "$FOO") as an emitter.
167             # We will look in that Environment variable for
168             # a callable to use as the actual emitter.
169             var = SCons.Util.get_environment_var(emitter)
170             if not var:
171                 raise UserError, "Supplied emitter '%s' does not appear to refer to an Environment variable" % emitter
172             kw['emitter'] = EmitterProxy(var)
173         elif SCons.Util.is_Dict(emitter):
174             kw['emitter'] = DictEmitter(emitter)
175
176     if kw.has_key('src_builder'):
177         ret = apply(MultiStepBuilder, (), kw)
178     else:
179         ret = apply(BuilderBase, (), kw)
180
181     if composite:
182         ret = CompositeBuilder(ret, composite)
183
184     return ret
185
186 def _init_nodes(builder, env, overrides, tlist, slist):
187     """Initialize lists of target and source nodes with all of
188     the proper Builder information.
189     """
190
191     # First, figure out if there are any errors in the way the targets
192     # were specified.
193     for t in tlist:
194         if t.side_effect:
195             raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
196         if t.has_builder():
197             if t.env != env:
198                 raise UserError, "Two different environments were specified for the same target: %s"%str(t)
199             elif t.overrides != overrides:
200                 raise UserError, "Two different sets of overrides were specified for the same target: %s"%str(t)
201             elif builder.scanner and builder.scanner != t.target_scanner:
202                 raise UserError, "Two different scanners were specified for the same target: %s"%str(t)
203
204             if builder.multi:
205                 if t.builder != builder:
206                     if isinstance(t.builder, ListBuilder) and isinstance(builder, ListBuilder) and t.builder.builder == builder.builder:
207                         raise UserError, "Two different target sets have a target in common: %s"%str(t)
208                     else:
209                         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))
210             elif t.sources != slist:
211                 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
212
213     # The targets are fine, so find or make the appropriate Executor to
214     # build this particular list of targets from this particular list of
215     # sources.
216     executor = None
217     if builder.multi:
218         try:
219             executor = tlist[0].get_executor(create = 0)
220         except AttributeError:
221             pass
222         else:
223             executor.add_sources(slist)
224     if executor is None:
225         executor = SCons.Executor.Executor(builder,
226                                            tlist[0].generate_build_env(env),
227                                            overrides,
228                                            tlist,
229                                            slist)
230
231     # Now set up the relevant information in the target Nodes themselves.
232     for t in tlist:
233         t.overrides = overrides
234         t.cwd = SCons.Node.FS.default_fs.getcwd()
235         t.builder_set(builder)
236         t.env_set(env)
237         t.add_source(slist)
238         t.set_executor(executor)
239         if builder.scanner:
240             t.target_scanner = builder.scanner
241
242     # Last, add scanners from the Environment to the source Nodes.
243     for s in slist:
244         src_key = s.scanner_key()        # the file suffix
245         scanner = env.get_scanner(src_key)
246         if scanner:
247             s.source_scanner = scanner
248
249 class EmitterProxy:
250     """This is a callable class that can act as a
251     Builder emitter.  It holds on to a string that
252     is a key into an Environment dictionary, and will
253     look there at actual build time to see if it holds
254     a callable.  If so, we will call that as the actual
255     emitter."""
256     def __init__(self, var):
257         self.var = SCons.Util.to_String(var)
258
259     def __call__(self, target, source, env):
260         emitter = self.var
261
262         # Recursively substitute the variable.
263         # We can't use env.subst() because it deals only
264         # in strings.  Maybe we should change that?
265         while SCons.Util.is_String(emitter) and \
266               env.has_key(emitter):
267             emitter = env[emitter]
268         if not callable(emitter):
269             return (target, source)
270
271         return emitter(target, source, env)
272
273     def __cmp__(self, other):
274         return cmp(self.var, other.var)
275
276 class BuilderBase:
277     """Base class for Builders, objects that create output
278     nodes (files) from input nodes (files).
279     """
280
281     def __init__(self,  action = None,
282                         prefix = '',
283                         suffix = '',
284                         src_suffix = '',
285                         node_factory = SCons.Node.FS.default_fs.File,
286                         target_factory = None,
287                         source_factory = None,
288                         scanner = None,
289                         emitter = None,
290                         multi = 0,
291                         env = None,
292                         overrides = {}):
293         self.action = SCons.Action.Action(action)
294         self.multi = multi
295         if SCons.Util.is_Dict(prefix):
296             prefix = Selector(prefix)
297         self.prefix = prefix
298         if SCons.Util.is_Dict(suffix):
299             suffix = Selector(suffix)
300         self.suffix = suffix
301         self.env = env
302         self.overrides = overrides
303
304         self.set_src_suffix(src_suffix)
305
306         self.target_factory = target_factory or node_factory
307         self.source_factory = source_factory or node_factory
308         self.scanner = scanner
309
310         self.emitter = emitter
311
312     def __nonzero__(self):
313         raise InternalError, "Do not test for the Node.builder attribute directly; use Node.has_builder() instead"
314
315     def get_name(self, env):
316         """Attempts to get the name of the Builder.
317
318         Look at the BUILDERS variable of env, expecting it to be a
319         dictionary containing this Builder, and return the key of the
320         dictionary."""
321
322         try:
323             index = env['BUILDERS'].values().index(self)
324             return env['BUILDERS'].keys()[index]
325         except (AttributeError, KeyError, ValueError):
326             return str(self.__class__)
327
328     def __cmp__(self, other):
329         return cmp(self.__dict__, other.__dict__)
330
331     def splitext(self, path):
332         return SCons.Util.splitext(path)
333
334     def _create_nodes(self, env, overrides, target = None, source = None):
335         """Create and return lists of target and source nodes.
336         """
337         def adjustixes(files, pre, suf, self=self):
338             if not files:
339                 return []
340             ret = []
341             if not SCons.Util.is_List(files):
342                 files = [files]
343
344             for f in files:
345                 if SCons.Util.is_String(f):
346                     if pre:
347                         path, fn = os.path.split(os.path.normpath(f))
348                         if fn[:len(pre)] != pre:
349                             f = os.path.join(path, pre + fn)
350                     # Only append a suffix if the file does not have one.
351                     if suf and not self.splitext(f)[1]:
352                         if f[-len(suf):] != suf:
353                             f = f + suf
354                 ret.append(f)
355             return ret
356
357         env = env.Override(overrides)
358
359         src_suf = self.get_src_suffix(env)
360
361         source = adjustixes(source, None, src_suf)
362         slist = SCons.Node.arg2nodes(source, self.source_factory)
363
364         pre = self.get_prefix(env, slist)
365         suf = self.get_suffix(env, slist)
366
367         if target is None:
368             try:
369                 t_from_s = slist[0].target_from_source
370             except AttributeError:
371                 raise UserError("Do not know how to create a target from source `%s'" % slist[0])
372             tlist = [ t_from_s(pre, suf, self.splitext) ]
373         else:
374             target = adjustixes(target, pre, suf)
375             tlist = SCons.Node.arg2nodes(target, self.target_factory)
376
377         if self.emitter:
378             # The emitter is going to do str(node), but because we're
379             # being called *from* a builder invocation, the new targets
380             # don't yet have a builder set on them and will look like
381             # source files.  Fool the emitter's str() calls by setting
382             # up a temporary builder on the new targets.
383             new_targets = []
384             for t in tlist:
385                 if not t.is_derived():
386                     t.builder = self
387                     new_targets.append(t)
388         
389             target, source = self.emitter(target=tlist, source=slist, env=env)
390
391             # Now delete the temporary builders that we attached to any
392             # new targets, so that _init_nodes() doesn't do weird stuff
393             # to them because it thinks they already have builders.
394             for t in new_targets:
395                 if t.builder is self:
396                     # Only delete the temporary builder if the emitter
397                     # didn't change it on us.
398                     t.builder = None
399
400             # Have to call arg2nodes yet again, since it is legal for
401             # emitters to spit out strings as well as Node instances.
402             slist = SCons.Node.arg2nodes(source, self.source_factory)
403             tlist = SCons.Node.arg2nodes(target, self.target_factory)
404
405         return tlist, slist
406
407     def __call__(self, env, target = None, source = _null, **overrides):
408         if source is _null:
409             source = target
410             target = None
411         tlist, slist = self._create_nodes(env, overrides, target, source)
412
413         if len(tlist) == 1:
414             _init_nodes(self, env, overrides, tlist, slist)
415             tlist = tlist[0]
416         else:
417             _init_nodes(ListBuilder(self, env, tlist), env, overrides, tlist, slist)
418
419         return tlist
420
421     def adjust_suffix(self, suff):
422         if suff and not suff[0] in [ '.', '$' ]:
423             return '.' + suff
424         return suff
425
426     def get_prefix(self, env, sources=[]):
427         prefix = self.prefix
428         if callable(prefix):
429             prefix = prefix(env, sources)
430         return env.subst(prefix)
431
432     def get_suffix(self, env, sources=[]):
433         suffix = self.suffix
434         if callable(suffix):
435             suffix = suffix(env, sources)
436         else:
437             suffix = self.adjust_suffix(suffix)
438         return env.subst(suffix)
439
440     def src_suffixes(self, env):
441         return map(lambda x, s=self, e=env: e.subst(s.adjust_suffix(x)),
442                    self.src_suffix)
443
444     def set_src_suffix(self, src_suffix):
445         if not src_suffix:
446             src_suffix = []
447         elif not SCons.Util.is_List(src_suffix):
448             src_suffix = [ src_suffix ]
449         self.src_suffix = src_suffix
450
451     def get_src_suffix(self, env):
452         """Get the first src_suffix in the list of src_suffixes."""
453         ret = self.src_suffixes(env)
454         if not ret:
455             return ''
456         return ret[0]
457
458     def targets(self, node):
459         """Return the list of targets for this builder instance.
460
461         For most normal builders, this is just the supplied node.
462         """
463         return [ node ]
464
465     def add_emitter(self, suffix, emitter):
466         """Add a suffix-emitter mapping to this Builder.
467
468         This assumes that emitter has been initialized with an
469         appropriate dictionary type, and will throw a TypeError if
470         not, so the caller is responsible for knowing that this is an
471         appropriate method to call for the Builder in question.
472         """
473         self.emitter[suffix] = emitter
474
475 class ListBuilder(SCons.Util.Proxy):
476     """A Proxy to support building an array of targets (for example,
477     foo.o and foo.h from foo.y) from a single Action execution.
478     """
479
480     def __init__(self, builder, env, tlist):
481         SCons.Util.Proxy.__init__(self, builder)
482         self.builder = builder
483         self.scanner = builder.scanner
484         self.env = env
485         self.tlist = tlist
486         self.multi = builder.multi
487
488     def targets(self, node):
489         """Return the list of targets for this builder instance.
490         """
491         return self.tlist
492
493     def __cmp__(self, other):
494         return cmp(self.__dict__, other.__dict__)
495
496     def get_name(self, env):
497         """Attempts to get the name of the Builder."""
498
499         return "ListBuilder(%s)" % self.builder.get_name(env)
500
501 class MultiStepBuilder(BuilderBase):
502     """This is a builder subclass that can build targets in
503     multiple steps.  The src_builder parameter to the constructor
504     accepts a builder that is called to build sources supplied to
505     this builder.  The targets of that first build then become
506     the sources of this builder.
507
508     If this builder has a src_suffix supplied, then the src_builder
509     builder is NOT invoked if the suffix of a source file matches
510     src_suffix.
511     """
512     def __init__(self,  src_builder,
513                         action = None,
514                         prefix = '',
515                         suffix = '',
516                         src_suffix = '',
517                         node_factory = SCons.Node.FS.default_fs.File,
518                         target_factory = None,
519                         source_factory = None,
520                         scanner=None,
521                         emitter=None):
522         BuilderBase.__init__(self, action, prefix, suffix, src_suffix,
523                              node_factory, target_factory, source_factory,
524                              scanner, emitter)
525         if not SCons.Util.is_List(src_builder):
526             src_builder = [ src_builder ]
527         self.src_builder = src_builder
528         self.sdict = {}
529         self.cached_src_suffixes = {} # source suffixes keyed on id(env)
530
531     def __call__(self, env, target = None, source = _null, **kw):
532         if source is _null:
533             source = target
534             target = None
535
536         slist = SCons.Node.arg2nodes(source, self.source_factory)
537         final_sources = []
538
539         try:
540             sdict = self.sdict[id(env)]
541         except KeyError:
542             sdict = {}
543             self.sdict[id(env)] = sdict
544             for bld in self.src_builder:
545                 if SCons.Util.is_String(bld):
546                     try:
547                         bld = env['BUILDERS'][bld]
548                     except KeyError:
549                         continue
550                 for suf in bld.src_suffixes(env):
551                     sdict[suf] = bld
552
553         src_suffixes = self.src_suffixes(env)
554
555         for snode in slist:
556             path, ext = self.splitext(snode.get_abspath())
557             if sdict.has_key(ext):
558                 src_bld = sdict[ext]
559                 tgt = apply(src_bld, (env, path, snode), kw)
560                 # Only supply the builder with sources it is capable
561                 # of building.
562                 if SCons.Util.is_List(tgt):
563                     tgt = filter(lambda x, self=self, suf=src_suffixes:
564                                  self.splitext(SCons.Util.to_String(x))[1] in suf,
565                                  tgt)
566                 if not SCons.Util.is_List(tgt):
567                     final_sources.append(tgt)
568                 else:
569                     final_sources.extend(tgt)
570             else:
571                 final_sources.append(snode)
572
573         return apply(BuilderBase.__call__,
574                      (self, env, target, final_sources), kw)
575
576     def get_src_builders(self, env):
577         """Return all the src_builders for this Builder.
578
579         This is essentially a recursive descent of the src_builder "tree."
580         """
581         ret = []
582         for bld in self.src_builder:
583             if SCons.Util.is_String(bld):
584                 # All Environments should have a BUILDERS
585                 # variable, so no need to check for it.
586                 try:
587                     bld = env['BUILDERS'][bld]
588                 except KeyError:
589                     continue
590             ret.append(bld)
591         return ret
592
593     def src_suffixes(self, env):
594         """Return a list of the src_suffix attributes for all
595         src_builders of this Builder.
596         """
597         try:
598             return self.cached_src_suffixes[id(env)]
599         except KeyError:
600             suffixes = BuilderBase.src_suffixes(self, env)
601             for builder in self.get_src_builders(env):
602                 suffixes.extend(builder.src_suffixes(env))
603             self.cached_src_suffixes[id(env)] = suffixes
604             return suffixes
605
606 class CompositeBuilder(SCons.Util.Proxy):
607     """A Builder Proxy whose main purpose is to always have
608     a DictCmdGenerator as its action, and to provide access
609     to the DictCmdGenerator's add_action() method.
610     """
611
612     def __init__(self, builder, cmdgen):
613         SCons.Util.Proxy.__init__(self, builder)
614
615         # cmdgen should always be an instance of DictCmdGenerator.
616         self.cmdgen = cmdgen
617         self.builder = builder
618
619     def add_action(self, suffix, action):
620         self.cmdgen.add_action(suffix, action)
621         self.set_src_suffix(self.cmdgen.src_suffixes())
622         
623     def __cmp__(self, other):
624         return cmp(self.__dict__, other.__dict__)