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