Only execute an Action once for a List of targets.
[scons.git] / src / engine / SCons / Builder.py
1 """SCons.Builder
2
3 XXX
4
5 """
6
7 #
8 # Copyright (c) 2001 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.FS
40 import SCons.Util
41
42
43
44 def Builder(**kw):
45     """A factory for builder objects."""
46     if kw.has_key('action') and SCons.Util.is_Dict(kw['action']):
47         return apply(CompositeBuilder, (), kw)
48     elif kw.has_key('src_builder'):
49         return apply(MultiStepBuilder, (), kw)
50     else:
51         return apply(BuilderBase, (), kw)
52
53
54
55 def _init_nodes(builder, env, tlist, slist):
56     """Initialize lists of target and source nodes with all of
57     the proper Builder information.
58     """
59     for t in tlist:
60         t.cwd = SCons.Node.FS.default_fs.getcwd()       # XXX
61         t.builder_set(builder)
62         t.env_set(env)
63         t.add_source(slist)
64         if builder.scanner:
65             t.scanner_set(builder.scanner.instance(env))
66
67     for s in slist:
68         s.env_set(env, 1)
69         scanner = env.get_scanner(os.path.splitext(s.name)[1])
70         if scanner:
71             s.scanner_set(scanner.instance(env))
72
73
74
75 class BuilderBase:
76     """Base class for Builders, objects that create output
77     nodes (files) from input nodes (files).
78     """
79
80     def __init__(self,  name = None,
81                         action = None,
82                         prefix = '',
83                         suffix = '',
84                         src_suffix = '',
85                         node_factory = SCons.Node.FS.default_fs.File,
86                         scanner = None):
87         if name is None:
88             raise UserError, "You must specify a name for the builder."
89         self.name = name
90         self.action = SCons.Action.Action(action)
91
92         self.prefix = prefix
93         self.suffix = suffix
94         self.src_suffix = src_suffix
95         self.node_factory = node_factory
96         self.scanner = scanner
97         if self.suffix and self.suffix[0] not in '.$':
98             self.suffix = '.' + self.suffix
99         if self.src_suffix and self.src_suffix[0] not in '.$':
100             self.src_suffix = '.' + self.src_suffix
101
102     def __cmp__(self, other):
103         return cmp(self.__dict__, other.__dict__)
104
105     def _create_nodes(self, env, target = None, source = None):
106         """Create and return lists of target and source nodes.
107         """
108         def adjustixes(files, pre, suf):
109             ret = []
110             if SCons.Util.is_String(files):
111                 files = string.split(files)
112             if not SCons.Util.is_List(files):
113                 files = [files]
114             for f in files:
115                 if SCons.Util.is_String(f):
116                     if pre and f[:len(pre)] != pre:
117                         path, fn = os.path.split(os.path.normpath(f))
118                         f = os.path.join(path, pre + fn)
119                     if suf:
120                         if f[-len(suf):] != suf:
121                             f = f + suf
122                 ret.append(f)
123             return ret
124
125         tlist = SCons.Util.scons_str2nodes(adjustixes(target,
126                                                       env.subst(self.prefix),
127                                                       env.subst(self.suffix)),
128                                            self.node_factory)
129
130         slist = SCons.Util.scons_str2nodes(adjustixes(source,
131                                                       None,
132                                                       env.subst(self.src_suffix)),
133                                            self.node_factory)
134         return tlist, slist
135
136     def __call__(self, env, target = None, source = None):
137         tlist, slist = self._create_nodes(env, target, source)
138
139         if len(tlist) == 1:
140             _init_nodes(self, env, tlist, slist)
141             tlist = tlist[0]
142         else:
143             _init_nodes(ListBuilder(self, env, tlist), env, tlist, slist)
144
145         return tlist
146
147
148     def execute(self, **kw):
149         """Execute a builder's action to create an output object.
150         """
151         return apply(self.action.execute, (), kw)
152
153     def get_raw_contents(self, **kw):
154         """Fetch the "contents" of the builder's action.
155         """
156         return apply(self.action.get_raw_contents, (), kw)
157
158     def get_contents(self, **kw):
159         """Fetch the "contents" of the builder's action
160         (for signature calculation).
161         """
162         return apply(self.action.get_contents, (), kw)
163
164     def src_suffixes(self):
165         if self.src_suffix != '':
166             return [self.src_suffix]
167         return []
168
169     def targets(self, node):
170         """Return the list of targets for this builder instance.
171
172         For most normal builders, this is just the supplied node.
173         """
174         return [ node ]
175
176 class ListBuilder:
177     """This is technically not a Builder object, but a wrapper
178     around another Builder object.  This is designed to look
179     like a Builder object, though, for purposes of building an
180     array of targets from a single Action execution.
181     """
182
183     def __init__(self, builder, env, tlist):
184         self.builder = builder
185         self.scanner = builder.scanner
186         self.env = env
187         self.tlist = tlist
188
189     def execute(self, **kw):
190         if hasattr(self, 'status'):
191             return self.status
192         for t in self.tlist:
193             # unlink all targets before building any
194             t.remove()
195         kw['target'] = self.tlist[0]
196         self.status = apply(self.builder.execute, (), kw)
197         for t in self.tlist:
198             if not t is kw['target']:
199                 t.build()
200         return self.status
201
202     def get_raw_contents(self, **kw):
203         return apply(self.builder.get_raw_contents, (), kw)
204
205     def get_contents(self, **kw):
206         return apply(self.builder.get_contents, (), kw)
207
208     def src_suffixes(self):
209         return self.builder.src_suffixes()
210
211     def targets(self, node):
212         """Return the list of targets for this builder instance.
213         """
214         return self.tlist
215
216 class MultiStepBuilder(BuilderBase):
217     """This is a builder subclass that can build targets in
218     multiple steps.  The src_builder parameter to the constructor
219     accepts a builder that is called to build sources supplied to
220     this builder.  The targets of that first build then become
221     the sources of this builder.
222
223     If this builder has a src_suffix supplied, then the src_builder
224     builder is NOT invoked if the suffix of a source file matches
225     src_suffix.
226     """
227     def __init__(self,  src_builder,
228                         name = None,
229                         action = None,
230                         prefix = '',
231                         suffix = '',
232                         src_suffix = '',
233                         node_factory = SCons.Node.FS.default_fs.File,
234                         scanner=None):
235         BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
236                              node_factory, scanner)
237         self.src_builder = src_builder
238
239     def __call__(self, env, target = None, source = None):
240         slist = SCons.Util.scons_str2nodes(source, self.node_factory)
241         final_sources = []
242         src_suffix = env.subst(self.src_suffix)
243         for snode in slist:
244             path, ext = os.path.splitext(snode.abspath)
245             if not src_suffix or ext != src_suffix:
246                 tgt = self.src_builder(env, target = [ path ],
247                                      source=snode)
248                 if not SCons.Util.is_List(tgt):
249                     final_sources.append(tgt)
250                 else:
251                     final_sources.extend(tgt)
252             else:
253                 final_sources.append(snode)
254         return BuilderBase.__call__(self, env, target=target,
255                                     source=final_sources)
256
257     def src_suffixes(self):
258         return BuilderBase.src_suffixes(self) + self.src_builder.src_suffixes()
259
260 class CompositeBuilder(BuilderBase):
261     """This is a convenient Builder subclass that can build different
262     files based on their suffixes.  For each target, this builder
263     will examine the target's sources.  If they are all the same
264     suffix, and that suffix is equal to one of the child builders'
265     src_suffix, then that child builder will be used.  Otherwise,
266     UserError is thrown."""
267     def __init__(self,  name = None,
268                         prefix='',
269                         suffix='',
270                         action = {},
271                         src_builder = []):
272         BuilderBase.__init__(self, name=name, prefix=prefix,
273                              suffix=suffix)
274         if src_builder and not SCons.Util.is_List(src_builder):
275             src_builder = [src_builder]
276         self.src_builder = src_builder
277         self.builder_dict = {}
278         for suff, act in action.items():
279              # Create subsidiary builders for every suffix in the
280              # action dictionary.  If there's a src_builder that
281              # matches the suffix, add that to the initializing
282              # keywords so that a MultiStepBuilder will get created.
283              kw = {'name' : name, 'action' : act, 'src_suffix' : suff}
284              src_bld = filter(lambda x, s=suff: x.suffix == s, self.src_builder)
285              if src_bld:
286                  kw['src_builder'] = src_bld[0]
287              self.builder_dict[suff] = apply(Builder, (), kw)
288
289     def __call__(self, env, target = None, source = None):
290         tlist, slist = BuilderBase._create_nodes(self, env,
291                                                  target=target, source=source)
292
293         # XXX These [bs]dict tables are invariant for each unique
294         # CompositeBuilder + Environment pair, so we should cache them.
295         bdict = {}
296         sdict = {}
297         for suffix, bld in self.builder_dict.items():
298             bdict[env.subst(bld.src_suffix)] = bld
299             sdict[suffix] = suffix
300             for s in bld.src_suffixes():
301                 bdict[s] = bld
302                 sdict[s] = suffix
303
304         for tnode in tlist:
305             suflist = map(lambda x, s=sdict: s[os.path.splitext(x.path)[1]],
306                           slist)
307             last_suffix=''
308             for suffix in suflist:
309                 if last_suffix and last_suffix != suffix:
310                     raise UserError, "The builder for %s can only build source files of identical suffixes:  %s." % (tnode.path, str(map(lambda t: str(t.path), tnode.sources)))
311                 last_suffix = suffix
312             if last_suffix:
313                 try:
314                     bdict[last_suffix].__call__(env, target = tnode,
315                                                 source = slist)
316                 except KeyError:
317                     raise UserError, "The builder for %s can not build files with suffix: %s" % (tnode.path, suffix)
318
319         if len(tlist) == 1:
320             tlist = tlist[0]
321         return tlist
322
323     def src_suffixes(self):
324         return reduce(lambda x, y: x + y,
325                       map(lambda b: b.src_suffixes(),
326                           self.builder_dict.values()))