8 # Copyright (c) 2001 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."""
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)
51 return apply(BuilderBase, (), kw)
55 def _init_nodes(builder, env, tlist, slist):
56 """Initialize lists of target and source nodes with all of
57 the proper Builder information.
60 t.cwd = SCons.Node.FS.default_fs.getcwd() # XXX
61 t.builder_set(builder)
65 t.scanner_set(builder.scanner.instance(env))
69 scanner = env.get_scanner(os.path.splitext(s.name)[1])
71 s.scanner_set(scanner.instance(env))
76 """Base class for Builders, objects that create output
77 nodes (files) from input nodes (files).
80 def __init__(self, name = None,
85 node_factory = SCons.Node.FS.default_fs.File,
88 raise UserError, "You must specify a name for the builder."
90 self.action = SCons.Action.Action(action)
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
102 def __cmp__(self, other):
103 return cmp(self.__dict__, other.__dict__)
105 def _create_nodes(self, env, target = None, source = None):
106 """Create and return lists of target and source nodes.
108 def adjustixes(files, pre, suf):
110 if SCons.Util.is_String(files):
111 files = string.split(files)
112 if not SCons.Util.is_List(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)
120 if f[-len(suf):] != suf:
125 tlist = SCons.Util.scons_str2nodes(adjustixes(target,
126 env.subst(self.prefix),
127 env.subst(self.suffix)),
130 slist = SCons.Util.scons_str2nodes(adjustixes(source,
132 env.subst(self.src_suffix)),
136 def __call__(self, env, target = None, source = None):
137 tlist, slist = self._create_nodes(env, target, source)
140 _init_nodes(self, env, tlist, slist)
143 _init_nodes(ListBuilder(self, env, tlist), env, tlist, slist)
148 def execute(self, **kw):
149 """Execute a builder's action to create an output object.
151 return apply(self.action.execute, (), kw)
153 def get_raw_contents(self, **kw):
154 """Fetch the "contents" of the builder's action.
156 return apply(self.action.get_raw_contents, (), kw)
158 def get_contents(self, **kw):
159 """Fetch the "contents" of the builder's action
160 (for signature calculation).
162 return apply(self.action.get_contents, (), kw)
164 def src_suffixes(self):
165 if self.src_suffix != '':
166 return [self.src_suffix]
169 def targets(self, node):
170 """Return the list of targets for this builder instance.
172 For most normal builders, this is just the supplied node.
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.
183 def __init__(self, builder, env, tlist):
184 self.builder = builder
185 self.scanner = builder.scanner
189 def execute(self, **kw):
190 if hasattr(self, 'status'):
193 # unlink all targets before building any
195 kw['target'] = self.tlist[0]
196 self.status = apply(self.builder.execute, (), kw)
198 if not t is kw['target']:
202 def get_raw_contents(self, **kw):
203 return apply(self.builder.get_raw_contents, (), kw)
205 def get_contents(self, **kw):
206 return apply(self.builder.get_contents, (), kw)
208 def src_suffixes(self):
209 return self.builder.src_suffixes()
211 def targets(self, node):
212 """Return the list of targets for this builder instance.
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.
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
227 def __init__(self, src_builder,
233 node_factory = SCons.Node.FS.default_fs.File,
235 BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
236 node_factory, scanner)
237 self.src_builder = src_builder
239 def __call__(self, env, target = None, source = None):
240 slist = SCons.Util.scons_str2nodes(source, self.node_factory)
242 src_suffix = env.subst(self.src_suffix)
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 ],
248 if not SCons.Util.is_List(tgt):
249 final_sources.append(tgt)
251 final_sources.extend(tgt)
253 final_sources.append(snode)
254 return BuilderBase.__call__(self, env, target=target,
255 source=final_sources)
257 def src_suffixes(self):
258 return BuilderBase.src_suffixes(self) + self.src_builder.src_suffixes()
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,
272 BuilderBase.__init__(self, name=name, prefix=prefix,
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)
286 kw['src_builder'] = src_bld[0]
287 self.builder_dict[suff] = apply(Builder, (), kw)
289 def __call__(self, env, target = None, source = None):
290 tlist, slist = BuilderBase._create_nodes(self, env,
291 target=target, source=source)
293 # XXX These [bs]dict tables are invariant for each unique
294 # CompositeBuilder + Environment pair, so we should cache them.
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():
305 suflist = map(lambda x, s=sdict: s[os.path.splitext(x.path)[1]],
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)))
314 bdict[last_suffix].__call__(env, target = tnode,
317 raise UserError, "The builder for %s can not build files with suffix: %s" % (tnode.path, suffix)
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()))