8 # Copyright (c) 2001, 2002 Steven Knight
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:
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
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.
30 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
36 from Errors import UserError
45 """A factory for builder objects."""
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'])
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)
58 return apply(BuilderBase, (), kw)
62 def _init_nodes(builder, env, tlist, slist):
63 """Initialize lists of target and source nodes with all of
64 the proper Builder information.
67 src_key = slist[0].scanner_key() # the file suffix
68 scanner = env.get_scanner(src_key)
70 s.source_scanner = scanner
73 t.cwd = SCons.Node.FS.default_fs.getcwd() # XXX
74 t.builder_set(builder)
78 t.target_scanner = builder.scanner
81 """Base class for Builders, objects that create output
82 nodes (files) from input nodes (files).
85 def __init__(self, name = None,
90 node_factory = SCons.Node.FS.default_fs.File,
91 target_factory = None,
92 source_factory = None,
95 raise UserError, "You must specify a name for the builder."
97 self.action = SCons.Action.Action(action)
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
110 def __cmp__(self, other):
111 return cmp(self.__dict__, other.__dict__)
113 def _create_nodes(self, env, target = None, source = None):
114 """Create and return lists of target and source nodes.
116 def adjustixes(files, pre, suf):
118 if SCons.Util.is_String(files):
119 files = string.split(files)
120 if not SCons.Util.is_List(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)
128 if f[-len(suf):] != suf:
133 tlist = SCons.Node.arg2nodes(adjustixes(target,
134 env.subst(self.prefix),
135 env.subst(self.suffix)),
138 slist = SCons.Node.arg2nodes(adjustixes(source,
140 env.subst(self.src_suffix)),
144 def __call__(self, env, target = None, source = None):
145 tlist, slist = self._create_nodes(env, target, source)
148 _init_nodes(self, env, tlist, slist)
151 _init_nodes(ListBuilder(self, env, tlist), env, tlist, slist)
156 def execute(self, **kw):
157 """Execute a builder's action to create an output object.
159 return apply(self.action.execute, (), kw)
161 def get_raw_contents(self, **kw):
162 """Fetch the "contents" of the builder's action.
164 return apply(self.action.get_raw_contents, (), kw)
166 def get_contents(self, **kw):
167 """Fetch the "contents" of the builder's action
168 (for signature calculation).
170 return apply(self.action.get_contents, (), kw)
172 def src_suffixes(self, env):
173 if self.src_suffix != '':
174 return [env.subst(self.src_suffix)]
177 def targets(self, node):
178 """Return the list of targets for this builder instance.
180 For most normal builders, this is just the supplied node.
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.
191 def __init__(self, builder, env, tlist):
192 self.builder = builder
193 self.scanner = builder.scanner
197 def execute(self, **kw):
198 if hasattr(self, 'status'):
201 # unlink all targets and make all directories
202 # before building anything
204 kw['target'] = self.tlist[0]
205 self.status = apply(self.builder.execute, (), kw)
207 if not t is kw['target']:
211 def get_raw_contents(self, **kw):
212 return apply(self.builder.get_raw_contents, (), kw)
214 def get_contents(self, **kw):
215 return apply(self.builder.get_contents, (), kw)
217 def src_suffixes(self, env):
218 return self.builder.src_suffixes(env)
220 def targets(self, node):
221 """Return the list of targets for this builder instance.
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.
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
236 def __init__(self, src_builder,
242 node_factory = SCons.Node.FS.default_fs.File,
243 target_factory = None,
244 source_factory = None,
246 BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
247 node_factory, target_factory, source_factory,
249 self.src_builder = src_builder
251 def __call__(self, env, target = None, source = None):
252 slist = SCons.Node.arg2nodes(source, self.source_factory)
254 src_suffix = env.subst(self.src_suffix)
256 for suff in self.src_builder.src_suffixes(env):
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)
265 final_sources.extend(tgt)
267 final_sources.append(snode)
268 return BuilderBase.__call__(self, env, target=target,
269 source=final_sources)
271 def src_suffixes(self, env):
272 return BuilderBase.src_suffixes(self, env) + \
273 self.src_builder.src_suffixes(env)
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,
287 BuilderBase.__init__(self, name=name, prefix=prefix,
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
296 def __call__(self, env, target = None, source = None):
297 tlist, slist = BuilderBase._create_nodes(self, env,
298 target=target, source=source)
301 if not self.sdict.has_key(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,
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
315 sufflist = map(lambda x, s=self.sdict[r]:
316 s[os.path.splitext(x.path)[1]],
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." % \
323 str(map(lambda t: str(t.path), tlist[0].sources)))
329 'action' : self.action_dict[last_suffix],
330 'src_suffix' : last_suffix,
333 sb = filter(lambda x, e=env, s=last_suffix:
334 e.subst(x.suffix) == s,
337 kw['src_builder'] = sb[0]
338 # XXX We should be able to cache this
339 bld = apply(Builder, (), kw)
341 bld.__call__(env, target = tnode, source = slist)
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),