Fix adding a prefix when the target isn't specified.
[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
47
48 import os.path
49 from SCons.Errors import InternalError, UserError
50
51 import SCons.Action
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("Cannot build multiple sources with different extensions: %s, %s" % (ext, my_ext))
86             ext = my_ext
87
88         if ext is None:
89             raise UserError("Cannot deduce file extension from source files: %s" % repr(map(str, source)))
90         try:
91             # XXX Do we need to perform Environment substitution
92             # on the keys of action_dict before looking it up?
93             return self.action_dict[ext]
94         except KeyError:
95             raise UserError("Don't know how to build a file with suffix %s." % ext)
96     def __cmp__(self, other):
97         return cmp(self.action_dict, other.action_dict)
98
99 def Builder(**kw):
100     """A factory for builder objects."""
101     composite = None
102     if kw.has_key('name'):
103         SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
104                             "The use of the 'name' parameter to Builder() is deprecated.")
105     if kw.has_key('generator'):
106         if kw.has_key('action'):
107             raise UserError, "You must not specify both an action and a generator."
108         kw['action'] = SCons.Action.CommandGenerator(kw['generator'])
109         del kw['generator']
110     elif kw.has_key('action') and SCons.Util.is_Dict(kw['action']):
111         composite = DictCmdGenerator(kw['action'])
112         kw['action'] = SCons.Action.CommandGenerator(composite)
113         kw['src_suffix'] = composite.src_suffixes()
114
115     if kw.has_key('emitter') and \
116        SCons.Util.is_String(kw['emitter']):
117         # This allows users to pass in an Environment
118         # variable reference (like "$FOO") as an emitter.
119         # We will look in that Environment variable for
120         # a callable to use as the actual emitter.
121         var = SCons.Util.get_environment_var(kw['emitter'])
122         if not var:
123             raise UserError, "Supplied emitter '%s' does not appear to refer to an Environment variable" % kw['emitter']
124         kw['emitter'] = EmitterProxy(var)
125
126     if kw.has_key('src_builder'):
127         ret = apply(MultiStepBuilder, (), kw)
128     else:
129         ret = apply(BuilderBase, (), kw)
130
131     if composite:
132         ret = CompositeBuilder(ret, composite)
133
134     return ret
135
136 def _init_nodes(builder, env, overrides, tlist, slist):
137     """Initialize lists of target and source nodes with all of
138     the proper Builder information.
139     """
140
141     for s in slist:
142         src_key = s.scanner_key()        # the file suffix
143         scanner = env.get_scanner(src_key)
144         if scanner:
145             s.source_scanner = scanner
146
147     for t in tlist:
148         if t.side_effect:
149             raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
150         if t.has_builder():
151             if t.env != env:
152                 raise UserError, "Two different environments were specified for the same target: %s"%str(t)
153             elif t.overrides != overrides:
154                 raise UserError, "Two different sets of overrides were specified for the same target: %s"%str(t)
155             elif builder.scanner and builder.scanner != t.target_scanner:
156                 raise UserError, "Two different scanners were specified for the same target: %s"%str(t)
157
158             if builder.multi:
159                 if t.builder != builder:
160                     if isinstance(t.builder, ListBuilder) and isinstance(builder, ListBuilder) and t.builder.builder == builder.builder:
161                         raise UserError, "Two different target sets have a target in common: %s"%str(t)
162                     else:
163                         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))
164             elif t.sources != slist:
165                 raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t)
166
167         t.overrides = overrides
168         t.cwd = SCons.Node.FS.default_fs.getcwd()
169         t.builder_set(builder)
170         t.env_set(env)
171         t.add_source(slist)
172         if builder.scanner:
173             t.target_scanner = builder.scanner
174
175 def _adjust_suffix(suff):
176     if suff and not suff[0] in [ '.', '$' ]:
177         return '.' + suff
178     return suff
179
180 class EmitterProxy:
181     """This is a callable class that can act as a
182     Builder emitter.  It holds on to a string that
183     is a key into an Environment dictionary, and will
184     look there at actual build time to see if it holds
185     a callable.  If so, we will call that as the actual
186     emitter."""
187     def __init__(self, var):
188         self.var = SCons.Util.to_String(var)
189
190     def __call__(self, target, source, env):
191         emitter = self.var
192
193         # Recursively substitute the variable.
194         # We can't use env.subst() because it deals only
195         # in strings.  Maybe we should change that?
196         while SCons.Util.is_String(emitter) and \
197               env.has_key(emitter):
198             emitter = env[emitter]
199         if not callable(emitter):
200             return (target, source)
201
202         return emitter(target, source, env)
203
204     def __cmp__(self, other):
205         return cmp(self.var, other.var)
206
207 class BuilderBase:
208     """Base class for Builders, objects that create output
209     nodes (files) from input nodes (files).
210     """
211
212     def __init__(self,  name = None,
213                         action = None,
214                         prefix = '',
215                         suffix = '',
216                         src_suffix = '',
217                         node_factory = SCons.Node.FS.default_fs.File,
218                         target_factory = None,
219                         source_factory = None,
220                         scanner = None,
221                         emitter = None,
222                         multi = 0):
223         self.name = name
224         self.action = SCons.Action.Action(action)
225         self.multi = multi
226         self.prefix = prefix
227         self.suffix = suffix
228
229         self.set_src_suffix(src_suffix)
230
231         self.target_factory = target_factory or node_factory
232         self.source_factory = source_factory or node_factory
233         self.scanner = scanner
234
235         self.emitter = emitter
236
237     def __nonzero__(self):
238         raise InternalError, "Do not test for the Node.builder attribute directly; use Node.has_builder() instead"
239
240     def get_name(self, env):
241         """Attempts to get the name of the Builder.
242
243         If the Builder's name attribute is None, then we will look at
244         the BUILDERS variable of env, expecting it to be a dictionary
245         containing this Builder, and we will return the key of the
246         dictionary."""
247
248         if self.name:
249             return self.name
250         try:
251             index = env['BUILDERS'].values().index(self)
252             return env['BUILDERS'].keys()[index]
253         except (AttributeError, KeyError, ValueError):
254             return str(self.__class__)
255
256     def __cmp__(self, other):
257         return cmp(self.__dict__, other.__dict__)
258
259     def _create_nodes(self, env, overrides, target = None, source = None):
260         """Create and return lists of target and source nodes.
261         """
262         def adjustixes(files, pre, suf):
263             if not files:
264                 return []
265             ret = []
266             if not SCons.Util.is_List(files):
267                 files = [files]
268
269             for f in files:
270                 if SCons.Util.is_String(f):
271                     if pre and f[:len(pre)] != pre:
272                         path, fn = os.path.split(os.path.normpath(f))
273                         f = os.path.join(path, pre + fn)
274                     # Only append a suffix if the file does not have one.
275                     if suf and not SCons.Util.splitext(f)[1]:
276                         if f[-len(suf):] != suf:
277                             f = f + suf
278                 ret.append(f)
279             return ret
280
281         env = env.Override(overrides)
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             s = source[0]
290             if isinstance(s, SCons.Node.Node):
291                 s = os.path.split(str(s))[1]
292             target = [ pre + os.path.splitext(s)[0] + suf ]
293         else:
294             target = adjustixes(target, pre, suf)
295
296         if self.emitter:
297             # pass the targets and sources to the emitter as strings
298             # rather than nodes since str(node) doesn't work
299             # properly from any directory other than the top directory,
300             # and emitters are called "in" the SConscript directory:
301             target, source = self.emitter(target=target, source=source, env=env)
302
303         slist = SCons.Node.arg2nodes(source, self.source_factory)
304         tlist = SCons.Node.arg2nodes(target, self.target_factory)
305
306         return tlist, slist
307
308     def __call__(self, env, target = None, source = _null, **overrides):
309         if source is _null:
310             source = target
311             target = None
312         tlist, slist = self._create_nodes(env, overrides, target, source)
313
314         if len(tlist) == 1:
315             _init_nodes(self, env, overrides, tlist, slist)
316             tlist = tlist[0]
317         else:
318             _init_nodes(ListBuilder(self, env, tlist), env, overrides, tlist, slist)
319
320         return tlist
321
322     def get_actions(self):
323         return self.action.get_actions()
324
325     def get_raw_contents(self, target, source, env):
326         """Fetch the "contents" of the builder's action.
327         """
328         return self.action.get_raw_contents(target, source, env)
329
330     def get_contents(self, target, source, env):
331         """Fetch the "contents" of the builder's action
332         (for signature calculation).
333         """
334         return self.action.get_contents(target, source, env)
335
336     def src_suffixes(self, env):
337         return map(lambda x, e=env: e.subst(_adjust_suffix(x)),
338                    self.src_suffix)
339
340     def set_src_suffix(self, src_suffix):
341         if not src_suffix:
342             src_suffix = []
343         elif not SCons.Util.is_List(src_suffix):
344             src_suffix = [ src_suffix ]
345         self.src_suffix = src_suffix
346
347     def get_src_suffix(self, env):
348         """Get the first src_suffix in the list of src_suffixes."""
349         ret = self.src_suffixes(env)
350         if not ret:
351             return ''
352         return ret[0]
353
354     def get_suffix(self, env):
355         return env.subst(_adjust_suffix(self.suffix))
356
357     def get_prefix(self, env):
358         return env.subst(self.prefix)
359
360     def targets(self, node):
361         """Return the list of targets for this builder instance.
362
363         For most normal builders, this is just the supplied node.
364         """
365         return [ node ]
366
367 class ListBuilder(SCons.Util.Proxy):
368     """A Proxy to support building an array of targets (for example,
369     foo.o and foo.h from foo.y) from a single Action execution.
370     """
371
372     def __init__(self, builder, env, tlist):
373         SCons.Util.Proxy.__init__(self, builder)
374         self.builder = builder
375         self.scanner = builder.scanner
376         self.env = env
377         self.tlist = tlist
378         self.multi = builder.multi
379         self.name = "ListBuilder(%s)"%builder.name
380
381     def targets(self, node):
382         """Return the list of targets for this builder instance.
383         """
384         return self.tlist
385
386     def __cmp__(self, other):
387         return cmp(self.__dict__, other.__dict__)
388
389     def get_name(self, env):
390         """Attempts to get the name of the Builder.
391
392         If the Builder's name attribute is None, then we will look at
393         the BUILDERS variable of env, expecting it to be a dictionary
394         containing this Builder, and we will return the key of the
395         dictionary."""
396
397         return "ListBuilder(%s)" % self.builder.get_name(env)
398
399 class MultiStepBuilder(BuilderBase):
400     """This is a builder subclass that can build targets in
401     multiple steps.  The src_builder parameter to the constructor
402     accepts a builder that is called to build sources supplied to
403     this builder.  The targets of that first build then become
404     the sources of this builder.
405
406     If this builder has a src_suffix supplied, then the src_builder
407     builder is NOT invoked if the suffix of a source file matches
408     src_suffix.
409     """
410     def __init__(self,  src_builder,
411                         name = None,
412                         action = None,
413                         prefix = '',
414                         suffix = '',
415                         src_suffix = '',
416                         node_factory = SCons.Node.FS.default_fs.File,
417                         target_factory = None,
418                         source_factory = None,
419                         scanner=None,
420                         emitter=None):
421         BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
422                              node_factory, target_factory, source_factory,
423                              scanner, emitter)
424         if not SCons.Util.is_List(src_builder):
425             src_builder = [ src_builder ]
426         self.src_builder = src_builder
427         self.sdict = {}
428         self.cached_src_suffixes = {} # source suffixes keyed on id(env)
429
430     def __call__(self, env, target = None, source = _null, **kw):
431         if source is _null:
432             source = target
433             target = None
434
435         slist = SCons.Node.arg2nodes(source, self.source_factory)
436         final_sources = []
437
438         try:
439             sdict = self.sdict[id(env)]
440         except KeyError:
441             sdict = {}
442             self.sdict[id(env)] = sdict
443             for bld in self.src_builder:
444                 if SCons.Util.is_String(bld):
445                     try:
446                         bld = env['BUILDERS'][bld]
447                     except KeyError:
448                         continue
449                 for suf in bld.src_suffixes(env):
450                     sdict[suf] = bld
451
452         src_suffixes = self.src_suffixes(env)
453
454         for snode in slist:
455             path, ext = SCons.Util.splitext(snode.abspath)
456             if sdict.has_key(ext):
457                 src_bld = sdict[ext]
458                 tgt = apply(src_bld, (env, path, snode), kw)
459                 # Only supply the builder with sources it is capable
460                 # of building.
461                 if SCons.Util.is_List(tgt):
462                     tgt = filter(lambda x, suf=src_suffixes:
463                                  SCons.Util.splitext(SCons.Util.to_String(x))[1] in suf,
464                                  tgt)
465                 if not SCons.Util.is_List(tgt):
466                     final_sources.append(tgt)
467                 else:
468                     final_sources.extend(tgt)
469             else:
470                 final_sources.append(snode)
471
472         return apply(BuilderBase.__call__,
473                      (self, env, target, final_sources), kw)
474
475     def get_src_builders(self, env):
476         """Return all the src_builders for this Builder.
477
478         This is essentially a recursive descent of the src_builder "tree."
479         """
480         ret = []
481         for bld in self.src_builder:
482             if SCons.Util.is_String(bld):
483                 # All Environments should have a BUILDERS
484                 # variable, so no need to check for it.
485                 try:
486                     bld = env['BUILDERS'][bld]
487                 except KeyError:
488                     continue
489             ret.append(bld)
490         return ret
491
492     def src_suffixes(self, env):
493         """Return a list of the src_suffix attributes for all
494         src_builders of this Builder.
495         """
496         try:
497             return self.cached_src_suffixes[id(env)]
498         except KeyError:
499             suffixes = BuilderBase.src_suffixes(self, env)
500             for builder in self.get_src_builders(env):
501                 suffixes.extend(builder.src_suffixes(env))
502             self.cached_src_suffixes[id(env)] = suffixes
503             return suffixes
504
505 class CompositeBuilder(SCons.Util.Proxy):
506     """A Builder Proxy whose main purpose is to always have
507     a DictCmdGenerator as its action, and to provide access
508     to the DictCmdGenerator's add_action() method.
509     """
510
511     def __init__(self, builder, cmdgen):
512         SCons.Util.Proxy.__init__(self, builder)
513
514         # cmdgen should always be an instance of DictCmdGenerator.
515         self.cmdgen = cmdgen
516         self.builder = builder
517
518     def add_action(self, suffix, action):
519         self.cmdgen.add_action(suffix, action)
520         self.set_src_suffix(self.cmdgen.src_suffixes())
521         
522     def __cmp__(self, other):
523         return cmp(self.__dict__, other.__dict__)