Support Java when using Repository and SConscriptChdir(0). (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__
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         slist = SCons.Node.arg2nodes(source, self.source_factory)
293         tlist = SCons.Node.arg2nodes(target, self.target_factory)
294
295         if self.emitter:
296             # The emitter is going to do str(node), but because we're
297             # being called *from* a builder invocation, the new targets
298             # don't yet have a builder set on them and will look like
299             # source files.  Fool the emitter's str() calls by setting
300             # up a temporary builder on the new targets.
301             new_targets = []
302             for t in tlist:
303                 if not t.is_derived():
304                     t.builder = self
305                     new_targets.append(t)
306         
307             target, source = self.emitter(target=tlist, source=slist, env=env)
308
309             # Now delete the temporary builders that we attached to the
310             # new targets, so that _init_nodes() doesn't do weird stuff
311             # to them because it thinks they already have builders.
312             for t in new_targets:
313                 t.builder = None
314
315             # Have to call arg2nodes yet again, since it is legal for
316             # emitters to spit out strings as well as Node instances.
317             slist = SCons.Node.arg2nodes(source, self.source_factory)
318             tlist = SCons.Node.arg2nodes(target, self.target_factory)
319
320         return tlist, slist
321
322     def __call__(self, env, target = None, source = _null, **overrides):
323         if source is _null:
324             source = target
325             target = None
326         tlist, slist = self._create_nodes(env, overrides, target, source)
327
328         if len(tlist) == 1:
329             _init_nodes(self, env, overrides, tlist, slist)
330             tlist = tlist[0]
331         else:
332             _init_nodes(ListBuilder(self, env, tlist), env, overrides, tlist, slist)
333
334         return tlist
335
336     def get_actions(self):
337         return self.action.get_actions()
338
339     def get_raw_contents(self, target, source, env):
340         """Fetch the "contents" of the builder's action.
341         """
342         return self.action.get_raw_contents(target, source, env)
343
344     def get_contents(self, target, source, env):
345         """Fetch the "contents" of the builder's action
346         (for signature calculation).
347         """
348         return self.action.get_contents(target, source, env)
349
350     def src_suffixes(self, env):
351         return map(lambda x, e=env: e.subst(_adjust_suffix(x)),
352                    self.src_suffix)
353
354     def set_src_suffix(self, src_suffix):
355         if not src_suffix:
356             src_suffix = []
357         elif not SCons.Util.is_List(src_suffix):
358             src_suffix = [ src_suffix ]
359         self.src_suffix = src_suffix
360
361     def get_src_suffix(self, env):
362         """Get the first src_suffix in the list of src_suffixes."""
363         ret = self.src_suffixes(env)
364         if not ret:
365             return ''
366         return ret[0]
367
368     def get_suffix(self, env):
369         return env.subst(_adjust_suffix(self.suffix))
370
371     def get_prefix(self, env):
372         return env.subst(self.prefix)
373
374     def targets(self, node):
375         """Return the list of targets for this builder instance.
376
377         For most normal builders, this is just the supplied node.
378         """
379         return [ node ]
380
381 class ListBuilder(SCons.Util.Proxy):
382     """A Proxy to support building an array of targets (for example,
383     foo.o and foo.h from foo.y) from a single Action execution.
384     """
385
386     def __init__(self, builder, env, tlist):
387         SCons.Util.Proxy.__init__(self, builder)
388         self.builder = builder
389         self.scanner = builder.scanner
390         self.env = env
391         self.tlist = tlist
392         self.multi = builder.multi
393
394     def targets(self, node):
395         """Return the list of targets for this builder instance.
396         """
397         return self.tlist
398
399     def __cmp__(self, other):
400         return cmp(self.__dict__, other.__dict__)
401
402     def get_name(self, env):
403         """Attempts to get the name of the Builder."""
404
405         return "ListBuilder(%s)" % self.builder.get_name(env)
406
407 class MultiStepBuilder(BuilderBase):
408     """This is a builder subclass that can build targets in
409     multiple steps.  The src_builder parameter to the constructor
410     accepts a builder that is called to build sources supplied to
411     this builder.  The targets of that first build then become
412     the sources of this builder.
413
414     If this builder has a src_suffix supplied, then the src_builder
415     builder is NOT invoked if the suffix of a source file matches
416     src_suffix.
417     """
418     def __init__(self,  src_builder,
419                         action = None,
420                         prefix = '',
421                         suffix = '',
422                         src_suffix = '',
423                         node_factory = SCons.Node.FS.default_fs.File,
424                         target_factory = None,
425                         source_factory = None,
426                         scanner=None,
427                         emitter=None):
428         BuilderBase.__init__(self, action, prefix, suffix, src_suffix,
429                              node_factory, target_factory, source_factory,
430                              scanner, emitter)
431         if not SCons.Util.is_List(src_builder):
432             src_builder = [ src_builder ]
433         self.src_builder = src_builder
434         self.sdict = {}
435         self.cached_src_suffixes = {} # source suffixes keyed on id(env)
436
437     def __call__(self, env, target = None, source = _null, **kw):
438         if source is _null:
439             source = target
440             target = None
441
442         slist = SCons.Node.arg2nodes(source, self.source_factory)
443         final_sources = []
444
445         try:
446             sdict = self.sdict[id(env)]
447         except KeyError:
448             sdict = {}
449             self.sdict[id(env)] = sdict
450             for bld in self.src_builder:
451                 if SCons.Util.is_String(bld):
452                     try:
453                         bld = env['BUILDERS'][bld]
454                     except KeyError:
455                         continue
456                 for suf in bld.src_suffixes(env):
457                     sdict[suf] = bld
458
459         src_suffixes = self.src_suffixes(env)
460
461         for snode in slist:
462             path, ext = SCons.Util.splitext(snode.abspath)
463             if sdict.has_key(ext):
464                 src_bld = sdict[ext]
465                 tgt = apply(src_bld, (env, path, snode), kw)
466                 # Only supply the builder with sources it is capable
467                 # of building.
468                 if SCons.Util.is_List(tgt):
469                     tgt = filter(lambda x, suf=src_suffixes:
470                                  SCons.Util.splitext(SCons.Util.to_String(x))[1] in suf,
471                                  tgt)
472                 if not SCons.Util.is_List(tgt):
473                     final_sources.append(tgt)
474                 else:
475                     final_sources.extend(tgt)
476             else:
477                 final_sources.append(snode)
478
479         return apply(BuilderBase.__call__,
480                      (self, env, target, final_sources), kw)
481
482     def get_src_builders(self, env):
483         """Return all the src_builders for this Builder.
484
485         This is essentially a recursive descent of the src_builder "tree."
486         """
487         ret = []
488         for bld in self.src_builder:
489             if SCons.Util.is_String(bld):
490                 # All Environments should have a BUILDERS
491                 # variable, so no need to check for it.
492                 try:
493                     bld = env['BUILDERS'][bld]
494                 except KeyError:
495                     continue
496             ret.append(bld)
497         return ret
498
499     def src_suffixes(self, env):
500         """Return a list of the src_suffix attributes for all
501         src_builders of this Builder.
502         """
503         try:
504             return self.cached_src_suffixes[id(env)]
505         except KeyError:
506             suffixes = BuilderBase.src_suffixes(self, env)
507             for builder in self.get_src_builders(env):
508                 suffixes.extend(builder.src_suffixes(env))
509             self.cached_src_suffixes[id(env)] = suffixes
510             return suffixes
511
512 class CompositeBuilder(SCons.Util.Proxy):
513     """A Builder Proxy whose main purpose is to always have
514     a DictCmdGenerator as its action, and to provide access
515     to the DictCmdGenerator's add_action() method.
516     """
517
518     def __init__(self, builder, cmdgen):
519         SCons.Util.Proxy.__init__(self, builder)
520
521         # cmdgen should always be an instance of DictCmdGenerator.
522         self.cmdgen = cmdgen
523         self.builder = builder
524
525     def add_action(self, suffix, action):
526         self.cmdgen.add_action(suffix, action)
527         self.set_src_suffix(self.cmdgen.src_suffixes())
528         
529     def __cmp__(self, other):
530         return cmp(self.__dict__, other.__dict__)