Fix --debug=tree for directory targets (Anthony Roach)
[scons.git] / src / engine / SCons / Builder.py
1 """SCons.Builder
2
3 XXX
4
5 """
6
7 #
8 # Copyright (c) 2001, 2002 Steven Knight
9 #
10 # Permission is hereby granted, free of charge, to any person obtaining
11 # a copy of this software and associated documentation files (the
12 # "Software"), to deal in the Software without restriction, including
13 # without limitation the rights to use, copy, modify, merge, publish,
14 # distribute, sublicense, and/or sell copies of the Software, and to
15 # permit persons to whom the Software is furnished to do so, subject to
16 # the following conditions:
17 #
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
20 #
21 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
22 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
23 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 #
29
30 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
31
32
33
34 import os.path
35 import string
36 from Errors import UserError
37
38 import SCons.Action
39 import SCons.Node
40 import SCons.Node.FS
41 import SCons.Util
42
43
44 def Builder(**kw):
45     """A factory for builder objects."""
46     
47     if kw.has_key('generator'):
48         if kw.has_key('action'):
49             raise UserError, "You must not specify both an action and a generator."
50         kw['action'] = SCons.Action.CommandGenerator(kw['generator'])
51         del kw['generator']
52
53     if kw.has_key('action') and SCons.Util.is_Dict(kw['action']):
54         return apply(CompositeBuilder, (), kw)
55     elif kw.has_key('src_builder'):
56         return apply(MultiStepBuilder, (), kw)
57     else:
58         return apply(BuilderBase, (), kw)
59
60
61
62 def _init_nodes(builder, env, tlist, slist):
63     """Initialize lists of target and source nodes with all of
64     the proper Builder information.
65     """
66     for s in slist:
67         src_key = slist[0].scanner_key()        # the file suffix
68         scanner = env.get_scanner(src_key)
69         if scanner:
70             s.source_scanner = scanner
71             
72     for t in tlist:
73         t.cwd = SCons.Node.FS.default_fs.getcwd()       # XXX
74         t.builder_set(builder)
75         t.env_set(env)
76         t.add_source(slist)
77         if builder.scanner:
78             t.target_scanner = builder.scanner
79
80 class BuilderBase:
81     """Base class for Builders, objects that create output
82     nodes (files) from input nodes (files).
83     """
84
85     def __init__(self,  name = None,
86                         action = None,
87                         prefix = '',
88                         suffix = '',
89                         src_suffix = '',
90                         node_factory = SCons.Node.FS.default_fs.File,
91                         target_factory = None,
92                         source_factory = None,
93                         scanner = None):
94         if name is None:
95             raise UserError, "You must specify a name for the builder."
96         self.name = name
97         self.action = SCons.Action.Action(action)
98
99         self.prefix = prefix
100         self.suffix = suffix
101         self.src_suffix = src_suffix
102         self.target_factory = target_factory or node_factory
103         self.source_factory = source_factory or node_factory
104         self.scanner = scanner
105         if self.suffix and self.suffix[0] not in '.$':
106             self.suffix = '.' + self.suffix
107         if self.src_suffix and self.src_suffix[0] not in '.$':
108             self.src_suffix = '.' + self.src_suffix
109
110     def __cmp__(self, other):
111         return cmp(self.__dict__, other.__dict__)
112
113     def _create_nodes(self, env, target = None, source = None):
114         """Create and return lists of target and source nodes.
115         """
116         def adjustixes(files, pre, suf):
117             ret = []
118             if SCons.Util.is_String(files):
119                 files = string.split(files)
120             if not SCons.Util.is_List(files):
121                 files = [files]
122             for f in files:
123                 if SCons.Util.is_String(f):
124                     if pre and f[:len(pre)] != pre:
125                         path, fn = os.path.split(os.path.normpath(f))
126                         f = os.path.join(path, pre + fn)
127                     if suf:
128                         if f[-len(suf):] != suf:
129                             f = f + suf
130                 ret.append(f)
131             return ret
132
133         tlist = SCons.Node.arg2nodes(adjustixes(target,
134                                                 env.subst(self.prefix),
135                                                 env.subst(self.suffix)),
136                                      self.target_factory)
137
138         slist = SCons.Node.arg2nodes(adjustixes(source,
139                                                 None,
140                                                 env.subst(self.src_suffix)),
141                                      self.source_factory)
142         return tlist, slist
143
144     def __call__(self, env, target = None, source = None):
145         tlist, slist = self._create_nodes(env, target, source)
146
147         if len(tlist) == 1:
148             _init_nodes(self, env, tlist, slist)
149             tlist = tlist[0]
150         else:
151             _init_nodes(ListBuilder(self, env, tlist), env, tlist, slist)
152
153         return tlist
154
155
156     def execute(self, **kw):
157         """Execute a builder's action to create an output object.
158         """
159         return apply(self.action.execute, (), kw)
160
161     def get_raw_contents(self, **kw):
162         """Fetch the "contents" of the builder's action.
163         """
164         return apply(self.action.get_raw_contents, (), kw)
165
166     def get_contents(self, **kw):
167         """Fetch the "contents" of the builder's action
168         (for signature calculation).
169         """
170         return apply(self.action.get_contents, (), kw)
171
172     def src_suffixes(self, env):
173         if self.src_suffix != '':
174             return [env.subst(self.src_suffix)]
175         return []
176
177     def targets(self, node):
178         """Return the list of targets for this builder instance.
179
180         For most normal builders, this is just the supplied node.
181         """
182         return [ node ]
183
184 class ListBuilder:
185     """This is technically not a Builder object, but a wrapper
186     around another Builder object.  This is designed to look
187     like a Builder object, though, for purposes of building an
188     array of targets from a single Action execution.
189     """
190
191     def __init__(self, builder, env, tlist):
192         self.builder = builder
193         self.scanner = builder.scanner
194         self.env = env
195         self.tlist = tlist
196
197     def execute(self, **kw):
198         if hasattr(self, 'status'):
199             return self.status
200         for t in self.tlist:
201             # unlink all targets and make all directories
202             # before building anything
203             t.prepare()
204         kw['target'] = self.tlist[0]
205         self.status = apply(self.builder.execute, (), kw)
206         for t in self.tlist:
207             if not t is kw['target']:
208                 t.build()
209         return self.status
210
211     def get_raw_contents(self, **kw):
212         return apply(self.builder.get_raw_contents, (), kw)
213
214     def get_contents(self, **kw):
215         return apply(self.builder.get_contents, (), kw)
216
217     def src_suffixes(self, env):
218         return self.builder.src_suffixes(env)
219
220     def targets(self, node):
221         """Return the list of targets for this builder instance.
222         """
223         return self.tlist
224
225 class MultiStepBuilder(BuilderBase):
226     """This is a builder subclass that can build targets in
227     multiple steps.  The src_builder parameter to the constructor
228     accepts a builder that is called to build sources supplied to
229     this builder.  The targets of that first build then become
230     the sources of this builder.
231
232     If this builder has a src_suffix supplied, then the src_builder
233     builder is NOT invoked if the suffix of a source file matches
234     src_suffix.
235     """
236     def __init__(self,  src_builder,
237                         name = None,
238                         action = None,
239                         prefix = '',
240                         suffix = '',
241                         src_suffix = '',
242                         node_factory = SCons.Node.FS.default_fs.File,
243                         target_factory = None,
244                         source_factory = None,
245                         scanner=None):
246         BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
247                              node_factory, target_factory, source_factory,
248                              scanner)
249         self.src_builder = src_builder
250
251     def __call__(self, env, target = None, source = None):
252         slist = SCons.Node.arg2nodes(source, self.source_factory)
253         final_sources = []
254         src_suffix = env.subst(self.src_suffix)
255         sdict = {}
256         for suff in self.src_builder.src_suffixes(env):
257             sdict[suff] = None
258         for snode in slist:
259             path, ext = os.path.splitext(snode.abspath)
260             if sdict.has_key(ext):
261                 tgt = self.src_builder(env, target = [ path ], source = snode)
262                 if not SCons.Util.is_List(tgt):
263                     final_sources.append(tgt)
264                 else:
265                     final_sources.extend(tgt)
266             else:
267                 final_sources.append(snode)
268         return BuilderBase.__call__(self, env, target=target,
269                                     source=final_sources)
270
271     def src_suffixes(self, env):
272         return BuilderBase.src_suffixes(self, env) + \
273                self.src_builder.src_suffixes(env)
274
275 class CompositeBuilder(BuilderBase):
276     """This is a convenient Builder subclass that can build different
277     files based on their suffixes.  For each target, this builder
278     will examine the target's sources.  If they are all the same
279     suffix, and that suffix is equal to one of the child builders'
280     src_suffix, then that child builder will be used.  Otherwise,
281     UserError is thrown."""
282     def __init__(self,  name = None,
283                         prefix='',
284                         suffix='',
285                         action = {},
286                         src_builder = []):
287         BuilderBase.__init__(self, name=name, prefix=prefix,
288                              suffix=suffix)
289         if src_builder and not SCons.Util.is_List(src_builder):
290             src_builder = [src_builder]
291         self.src_builder = src_builder
292         self.action_dict = action
293         self.sdict = {}
294         self.sbuild = {}
295
296     def __call__(self, env, target = None, source = None):
297         tlist, slist = BuilderBase._create_nodes(self, env,
298                                                  target=target, source=source)
299
300         r = repr(env)
301         if not self.sdict.has_key(r):
302             self.sdict[r] = {}
303             self.sbuild[r] = []
304             for suff in self.src_suffixes(env):
305                 suff = env.subst(suff)
306                 self.sdict[r][suff] = suff
307                 self.sbuild[r].extend(filter(lambda x, e=env, s=suff:
308                                                     e.subst(x.suffix) == s,
309                                              self.src_builder))
310             for sb in self.sbuild[r]:
311                 suff = env.subst(sb.suffix)
312                 for s in sb.src_suffixes(env):
313                      self.sdict[r][env.subst(s)] = suff
314
315         sufflist = map(lambda x, s=self.sdict[r]:
316                               s[os.path.splitext(x.path)[1]],
317                        slist)
318         last_suffix = ''
319         for suff in sufflist:
320             if last_suffix and last_suffix != suff:
321                 raise UserError, "The builder for %s can only build source files of identical suffixes:  %s." % \
322                       (tlist[0].path,
323                        str(map(lambda t: str(t.path), tlist[0].sources)))
324             last_suffix = suff
325
326         if last_suffix:
327             kw = {
328                 'name' : self.name,
329                 'action' : self.action_dict[last_suffix],
330                 'src_suffix' : last_suffix,
331             }
332             if self.sbuild[r]:
333                 sb = filter(lambda x, e=env, s=last_suffix:
334                                    e.subst(x.suffix) == s,
335                             self.sbuild[r])
336                 if sb:
337                     kw['src_builder'] = sb[0]
338             # XXX We should be able to cache this
339             bld = apply(Builder, (), kw)
340             for tnode in tlist:
341                 bld.__call__(env, target = tnode, source = slist)
342
343         if len(tlist) == 1:
344             tlist = tlist[0]
345         return tlist
346
347     def src_suffixes(self, env):
348         suffixes = map(lambda k, e=env: e.subst(k), self.action_dict.keys()) + \
349                    reduce(lambda x, y: x + y,
350                           map(lambda b, e=env: b.src_suffixes(e),
351                               self.src_builder),
352                           [])
353         return suffixes