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)
56 """Base class for Builders, objects that create output
57 nodes (files) from input nodes (files).
60 def __init__(self, name = None,
65 node_factory = SCons.Node.FS.default_fs.File,
68 raise UserError, "You must specify a name for the builder."
70 self.action = SCons.Action.Action(action)
74 self.src_suffix = src_suffix
75 self.node_factory = node_factory
76 self.scanner = scanner
77 if self.suffix and self.suffix[0] not in '.$':
78 self.suffix = '.' + self.suffix
79 if self.src_suffix and self.src_suffix[0] not in '.$':
80 self.src_suffix = '.' + self.src_suffix
82 def __cmp__(self, other):
83 return cmp(self.__dict__, other.__dict__)
85 def _create_nodes(self, env, target = None, source = None):
86 """Create and return lists of target and source nodes.
88 def adjustixes(files, pre, suf):
90 if SCons.Util.is_String(files):
91 files = string.split(files)
92 if not SCons.Util.is_List(files):
95 if SCons.Util.is_String(f):
96 if pre and f[:len(pre)] != pre:
97 path, fn = os.path.split(os.path.normpath(f))
98 f = os.path.join(path, pre + fn)
100 if f[-len(suf):] != suf:
105 tlist = SCons.Util.scons_str2nodes(adjustixes(target,
106 env.subst(self.prefix),
107 env.subst(self.suffix)),
110 slist = SCons.Util.scons_str2nodes(adjustixes(source,
112 env.subst(self.src_suffix)),
116 def _init_nodes(self, env, tlist, slist):
117 """Initialize lists of target and source nodes with all of
118 the proper Builder information.
121 t.cwd = SCons.Node.FS.default_fs.getcwd() # XXX
126 t.scanner_set(self.scanner.instance(env))
130 scanner = env.get_scanner(os.path.splitext(s.name)[1])
132 s.scanner_set(scanner.instance(env))
138 def __call__(self, env, target = None, source = None):
139 tlist, slist = self._create_nodes(env, target, source)
141 return self._init_nodes(env, tlist, slist)
143 def execute(self, **kw):
144 """Execute a builder's action to create an output object.
146 return apply(self.action.execute, (), kw)
148 def get_contents(self, **kw):
149 """Fetch the "contents" of the builder's action
150 (for signature calculation).
152 return apply(self.action.get_contents, (), kw)
154 def src_suffixes(self):
155 if self.src_suffix != '':
156 return [self.src_suffix]
159 class MultiStepBuilder(BuilderBase):
160 """This is a builder subclass that can build targets in
161 multiple steps. The src_builder parameter to the constructor
162 accepts a builder that is called to build sources supplied to
163 this builder. The targets of that first build then become
164 the sources of this builder.
166 If this builder has a src_suffix supplied, then the src_builder
167 builder is NOT invoked if the suffix of a source file matches
170 def __init__(self, src_builder,
176 node_factory = SCons.Node.FS.default_fs.File,
178 BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
179 node_factory, scanner)
180 self.src_builder = src_builder
182 def __call__(self, env, target = None, source = None):
183 slist = SCons.Util.scons_str2nodes(source, self.node_factory)
185 src_suffix = env.subst(self.src_suffix)
187 path, ext = os.path.splitext(snode.abspath)
188 if not src_suffix or ext != src_suffix:
189 tgt = self.src_builder(env, target = [ path ],
191 if not SCons.Util.is_List(tgt):
192 final_sources.append(tgt)
194 final_sources.extend(tgt)
196 final_sources.append(snode)
197 return BuilderBase.__call__(self, env, target=target,
198 source=final_sources)
200 def src_suffixes(self):
201 return BuilderBase.src_suffixes(self) + self.src_builder.src_suffixes()
203 class CompositeBuilder(BuilderBase):
204 """This is a convenient Builder subclass that can build different
205 files based on their suffixes. For each target, this builder
206 will examine the target's sources. If they are all the same
207 suffix, and that suffix is equal to one of the child builders'
208 src_suffix, then that child builder will be used. Otherwise,
209 UserError is thrown."""
210 def __init__(self, name = None,
215 BuilderBase.__init__(self, name=name, prefix=prefix,
217 if src_builder and not SCons.Util.is_List(src_builder):
218 src_builder = [src_builder]
219 self.src_builder = src_builder
220 self.builder_dict = {}
221 for suff, act in action.items():
222 # Create subsidiary builders for every suffix in the
223 # action dictionary. If there's a src_builder that
224 # matches the suffix, add that to the initializing
225 # keywords so that a MultiStepBuilder will get created.
226 kw = {'name' : name, 'action' : act, 'src_suffix' : suff}
227 src_bld = filter(lambda x, s=suff: x.suffix == s, self.src_builder)
229 kw['src_builder'] = src_bld[0]
230 self.builder_dict[suff] = apply(Builder, (), kw)
232 def __call__(self, env, target = None, source = None):
233 tlist, slist = BuilderBase._create_nodes(self, env,
234 target=target, source=source)
236 # XXX These [bs]dict tables are invariant for each unique
237 # CompositeBuilder + Environment pair, so we should cache them.
240 for suffix, bld in self.builder_dict.items():
241 bdict[env.subst(bld.src_suffix)] = bld
242 sdict[suffix] = suffix
243 for s in bld.src_suffixes():
248 suflist = map(lambda x, s=sdict: s[os.path.splitext(x.path)[1]],
251 for suffix in suflist:
252 if last_suffix and last_suffix != suffix:
253 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)))
257 bdict[last_suffix].__call__(env, target = tnode,
260 raise UserError, "The builder for %s can not build files with suffix: %s" % (tnode.path, suffix)
266 def src_suffixes(self):
267 return reduce(lambda x, y: x + y,
268 map(lambda b: b.src_suffixes(),
269 self.builder_dict.values()))