Deduce the target if it's not supplied.
[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 = os.path.splitext(src)[1]
86             if ext and my_ext != ext:
87                 raise UserError("Cannot build multiple sources with different extensions.")
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 os.path.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         if self.emitter:
287             # pass the targets and sources to the emitter as strings
288             # rather than nodes since str(node) doesn't work 
289             # properly from any directory other than the top directory,
290             # and emitters are called "in" the SConscript directory:
291             tlist = adjustixes(target, pre, suf)
292             slist = adjustixes(source, None, src_suf)
293
294             emit_args = { 'target' : tlist,
295                           'source' : slist,
296                           'env' : env }
297             emit_args.update(args)
298             target, source = apply(self.emitter, (), emit_args)
299
300         slist = SCons.Node.arg2nodes(adjustixes(source, None, src_suf),
301                                      self.source_factory)
302         if target is None:
303             target = map(lambda x, s=suf: os.path.splitext(str(x))[0] + s,
304                          slist)
305         tlist = SCons.Node.arg2nodes(adjustixes(target, pre, suf),
306                                      self.target_factory)
307
308         return tlist, slist
309
310     def __call__(self, env, target = None, source = _null, **kw):
311         if source is _null:
312             source = target
313             target = None
314         tlist, slist = self._create_nodes(env, kw, target, source)
315
316         if len(tlist) == 1:
317             _init_nodes(self, env, kw, tlist, slist)
318             tlist = tlist[0]
319         else:
320             _init_nodes(ListBuilder(self, env, tlist), env, kw, tlist, slist)
321
322         return tlist
323
324     def execute(self, **kw):
325         """Execute a builder's action to create an output object.
326         """
327         return apply(self.action.execute, (), kw)
328
329     def get_raw_contents(self, **kw):
330         """Fetch the "contents" of the builder's action.
331         """
332         return apply(self.action.get_raw_contents, (), kw)
333
334     def get_contents(self, **kw):
335         """Fetch the "contents" of the builder's action
336         (for signature calculation).
337         """
338         return apply(self.action.get_contents, (), kw)
339
340     def src_suffixes(self, env):
341         return map(lambda x, e=env: e.subst(_adjust_suffix(x)),
342                    self.src_suffix)
343
344     def set_src_suffix(self, src_suffix):
345         if not src_suffix:
346             src_suffix = []
347         elif not SCons.Util.is_List(src_suffix):
348             src_suffix = [ src_suffix ]
349         self.src_suffix = src_suffix
350
351     def get_src_suffix(self, env):
352         """Get the first src_suffix in the list of src_suffixes."""
353         ret = self.src_suffixes(env)
354         if not ret:
355             return ''
356         return ret[0]
357
358     def get_suffix(self, env):
359         return env.subst(_adjust_suffix(self.suffix))
360
361     def get_prefix(self, env):
362         return env.subst(self.prefix)
363
364     def targets(self, node):
365         """Return the list of targets for this builder instance.
366
367         For most normal builders, this is just the supplied node.
368         """
369         return [ node ]
370
371 class ListBuilder(SCons.Util.Proxy):
372     """A Proxy to support building an array of targets (for example,
373     foo.o and foo.h from foo.y) from a single Action execution.
374     """
375
376     def __init__(self, builder, env, tlist):
377         SCons.Util.Proxy.__init__(self, builder)
378         self.builder = builder
379         self.scanner = builder.scanner
380         self.env = env
381         self.tlist = tlist
382         self.multi = builder.multi
383         self.name = "ListBuilder(%s)"%builder.name
384
385     def execute(self, **kw):
386         if hasattr(self, 'status'):
387             return self.status
388         for t in self.tlist:
389             # unlink all targets and make all directories
390             # before building anything
391             t.prepare()
392         kw['target'] = self.tlist
393         self.status = apply(self.builder.execute, (), kw)
394         for t in self.tlist:
395             if not t is kw['target']:
396                 t.build()
397         return self.status
398
399     def targets(self, node):
400         """Return the list of targets for this builder instance.
401         """
402         return self.tlist
403
404     def __cmp__(self, other):
405         return cmp(self.__dict__, other.__dict__)
406
407     def get_name(self, env):
408         """Attempts to get the name of the Builder.
409
410         If the Builder's name attribute is None, then we will look at
411         the BUILDERS variable of env, expecting it to be a dictionary
412         containing this Builder, and we will return the key of the
413         dictionary."""
414
415         return "ListBuilder(%s)" % self.builder.get_name(env)
416
417 class MultiStepBuilder(BuilderBase):
418     """This is a builder subclass that can build targets in
419     multiple steps.  The src_builder parameter to the constructor
420     accepts a builder that is called to build sources supplied to
421     this builder.  The targets of that first build then become
422     the sources of this builder.
423
424     If this builder has a src_suffix supplied, then the src_builder
425     builder is NOT invoked if the suffix of a source file matches
426     src_suffix.
427     """
428     def __init__(self,  src_builder,
429                         name = None,
430                         action = None,
431                         prefix = '',
432                         suffix = '',
433                         src_suffix = '',
434                         node_factory = SCons.Node.FS.default_fs.File,
435                         target_factory = None,
436                         source_factory = None,
437                         scanner=None,
438                         emitter=None):
439         BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
440                              node_factory, target_factory, source_factory,
441                              scanner, emitter)
442         if not SCons.Util.is_List(src_builder):
443             src_builder = [ src_builder ]
444         self.src_builder = src_builder
445         self.sdict = {}
446
447     def __call__(self, env, target = None, source = None, **kw):
448         slist = SCons.Node.arg2nodes(source, self.source_factory)
449         final_sources = []
450
451         r=repr(env)
452         try:
453             sdict = self.sdict[r]
454         except KeyError:
455             sdict = {}
456             self.sdict[r] = sdict
457             for bld in self.src_builder:
458                 if SCons.Util.is_String(bld):
459                     try:
460                         bld = env['BUILDERS'][bld]
461                     except KeyError:
462                         continue
463                 for suf in bld.src_suffixes(env):
464                     sdict[suf] = bld
465                     
466         for snode in slist:
467             path, ext = os.path.splitext(snode.abspath)
468             if sdict.has_key(ext):
469                 src_bld = sdict[ext]
470
471                 dictArgs = copy.copy(kw)
472                 dictArgs['target'] = [path]
473                 dictArgs['source'] = snode
474                 dictArgs['env'] = env
475                 tgt = apply(src_bld, (), dictArgs)
476                 if not SCons.Util.is_List(tgt):
477                     tgt = [ tgt ]
478
479                 # Only supply the builder with sources it is capable
480                 # of building.
481                 tgt = filter(lambda x,
482                              suf=self.src_suffixes(env):
483                              os.path.splitext(SCons.Util.to_String(x))[1] in \
484                              suf, tgt)
485                 final_sources.extend(tgt)
486             else:
487                 final_sources.append(snode)
488         dictKwArgs = kw
489         dictKwArgs['target'] = target
490         dictKwArgs['source'] = final_sources
491         return apply(BuilderBase.__call__,
492                      (self, env), dictKwArgs)
493
494     def get_src_builders(self, env):
495         """Return all the src_builders for this Builder.
496
497         This is essentially a recursive descent of the src_builder "tree."
498         """
499         ret = []
500         for bld in self.src_builder:
501             if SCons.Util.is_String(bld):
502                 # All Environments should have a BUILDERS
503                 # variable, so no need to check for it.
504                 try:
505                     bld = env['BUILDERS'][bld]
506                 except KeyError:
507                     continue
508             ret.append(bld)
509         return ret
510
511     def src_suffixes(self, env):
512         """Return a list of the src_suffix attributes for all
513         src_builders of this Builder.
514         """
515         suffixes = BuilderBase.src_suffixes(self, env)
516         for builder in self.get_src_builders(env):
517             suffixes.extend(builder.src_suffixes(env))
518         return suffixes
519
520 class CompositeBuilder(SCons.Util.Proxy):
521     """A Builder Proxy whose main purpose is to always have
522     a DictCmdGenerator as its action, and to provide access
523     to the DictCmdGenerator's add_action() method.
524     """
525
526     def __init__(self, builder, cmdgen):
527         SCons.Util.Proxy.__init__(self, builder)
528
529         # cmdgen should always be an instance of DictCmdGenerator.
530         self.cmdgen = cmdgen
531         self.builder = builder
532
533     def add_action(self, suffix, action):
534         self.cmdgen.add_action(suffix, action)
535         self.set_src_suffix(self.cmdgen.src_suffixes())
536         
537     def __cmp__(self, other):
538         return cmp(self.__dict__, other.__dict__)