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