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__"
37 from SCons.Util import PathList, scons_str2nodes, scons_subst, scons_subst_list, autogenerate
40 from UserList import UserList
41 from UserDict import UserDict
42 from Errors import UserError
45 from UserString import UserString
52 if os.name == 'posix':
54 def spawn(cmd, args, env):
58 os.execvpe(cmd, args, env)
61 pid, stat = os.waitpid(pid, 0)
67 def pathsearch(cmd, env):
68 # In order to deal with the fact that 1.5.2 doesn't have
69 # os.spawnvpe(), roll our own PATH search.
70 if os.path.isabs(cmd):
71 if not os.path.exists(cmd):
73 if type(exts) != type([]):
74 exts = string.split(exts, os.pathsep)
81 if type(path) != type([]):
82 path = string.split(path, os.pathsep)
84 if type(exts) != type([]):
85 exts = string.split(exts, os.pathsep)
89 pairs.append((dir, e))
90 for dir, ext in pairs:
91 f = os.path.join(dir, cmd)
98 def spawn(cmd, args, env):
100 ret = os.spawnvpe(os.P_WAIT, cmd, args, env)
101 except AttributeError:
102 cmd = pathsearch(cmd, env)
103 ret = os.spawnve(os.P_WAIT, cmd, args, env)
109 """A factory for builder objects."""
110 if kw.has_key('src_builder'):
111 return apply(MultiStepBuilder, (), kw)
112 elif kw.has_key('action') and (type(kw['action']) is types.DictType or
113 isinstance(kw['action'], UserDict)):
114 action_dict = kw['action']
116 for suffix, action in action_dict.items():
118 bld_kw['action'] = action
119 bld_kw['src_suffix'] = suffix
120 builders.append(apply(BuilderBase, (), bld_kw))
122 kw['builders'] = builders
123 return apply(CompositeBuilder, (), kw)
125 return apply(BuilderBase, (), kw)
130 """Base class for Builders, objects that create output
131 nodes (files) from input nodes (files).
134 def __init__(self, name = None,
139 node_factory = SCons.Node.FS.default_fs.File,
142 self.action = Action(action)
146 self.src_suffix = src_suffix
147 self.node_factory = node_factory
148 self.scanner = scanner
149 if self.suffix and self.suffix[0] not in '.$':
150 self.suffix = '.' + self.suffix
151 if self.src_suffix and self.src_suffix[0] not in '.$':
152 self.src_suffix = '.' + self.src_suffix
154 def __cmp__(self, other):
155 return cmp(self.__dict__, other.__dict__)
157 def __call__(self, env, target = None, source = None):
158 def adjustixes(files, pre, suf):
160 if not type(files) is type([]):
163 if type(f) is types.StringType or isinstance(f, UserString):
164 if pre and f[:len(pre)] != pre:
165 path, fn = os.path.split(os.path.normpath(f))
166 f = os.path.join(path, pre + fn)
168 if f[-len(suf):] != suf:
173 tlist = scons_str2nodes(adjustixes(target,
174 env.subst(self.prefix),
175 env.subst(self.suffix)),
178 slist = scons_str2nodes(adjustixes(source, None,
179 env.subst(self.src_suffix)),
187 t.scanner_set(self.scanner.instance(env))
191 scanner = env.get_scanner(os.path.splitext(s.name)[1])
193 s.scanner_set(scanner.instance(env))
199 def execute(self, **kw):
200 """Execute a builder's action to create an output object.
202 return apply(self.action.execute, (), kw)
204 def get_contents(self, **kw):
205 """Fetch the "contents" of the builder's action
206 (for signature calculation).
208 return apply(self.action.get_contents, (), kw)
210 class MultiStepBuilder(BuilderBase):
211 """This is a builder subclass that can build targets in
212 multiple steps. The src_builder parameter to the constructor
213 accepts a builder that is called to build sources supplied to
214 this builder. The targets of that first build then become
215 the sources of this builder.
217 If this builder has a src_suffix supplied, then the src_builder
218 builder is NOT invoked if the suffix of a source file matches
221 def __init__(self, src_builder,
227 node_factory = SCons.Node.FS.default_fs.File,
229 BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
230 node_factory, scanner)
231 self.src_builder = src_builder
233 def __call__(self, env, target = None, source = None):
234 slist = scons_str2nodes(source, self.node_factory)
236 src_suffix = env.subst(self.src_suffix)
238 path, ext = os.path.splitext(snode.path)
239 if not src_suffix or ext != src_suffix:
240 tgt = self.src_builder(env, target = [ path ],
242 if not type(tgt) is types.ListType:
243 final_sources.append(tgt)
245 final_sources.extend(tgt)
247 final_sources.append(snode)
248 return BuilderBase.__call__(self, env, target=target,
249 source=final_sources)
251 class CompositeBuilder(BuilderBase):
252 """This is a convenient Builder subclass that can build different
253 files based on their suffixes. For each target, this builder
254 will examine the target's sources. If they are all the same
255 suffix, and that suffix is equal to one of the child builders'
256 src_suffix, then that child builder will be used. Otherwise,
259 Child builders are supplied via the builders arg to the
260 constructor. Each must have its src_suffix set."""
261 def __init__(self, name = None,
265 BuilderBase.__init__(self, name=name, prefix=prefix,
267 self.builder_dict = {}
269 if not bld.src_suffix:
270 raise InternalError, "All builders supplied to CompositeBuilder class must have a src_suffix."
271 self.builder_dict[bld.src_suffix] = bld
273 def __call__(self, env, target = None, source = None):
274 ret = BuilderBase.__call__(self, env, target=target, source=source)
277 for suffix, bld in self.builder_dict.items():
278 builder_dict[env.subst(bld.src_suffix)] = bld
280 if type(ret) is types.ListType:
285 suflist = map(lambda x: os.path.splitext(x.path)[1],
288 for suffix in suflist:
289 if last_suffix and last_suffix != suffix:
290 raise UserError, "The builder for %s is only capable of building source files of identical suffixes." % tnode.path
294 tnode.builder_set(builder_dict[last_suffix])
296 raise UserError, "Builder not capable of building files with suffix: %s" % suffix
303 """A factory for action objects."""
305 return FunctionAction(act)
306 elif type(act) == types.StringType or isinstance(act, UserString):
307 return CommandAction(act)
308 elif type(act) == types.ListType or isinstance(act, UserList):
309 return ListAction(act)
314 """Base class for actions that create output objects.
316 We currently expect Actions will only be accessible through
317 Builder objects, so they don't yet merit their own module."""
318 def __cmp__(self, other):
319 return cmp(self.__dict__, other.__dict__)
321 def show(self, string):
324 def subst_dict(self, **kw):
325 """Create a dictionary for substitution of construction
328 This translates the following special arguments:
330 env - the construction environment itself,
331 the values of which (CC, CCFLAGS, etc.)
332 are copied straight into the dictionary
334 target - the target (object or array of objects),
335 used to generate the TARGET and TARGETS
336 construction variables
338 source - the source (object or array of objects),
339 used to generate the SOURCES construction
342 Any other keyword arguments are copied into the
346 if kw.has_key('env'):
347 dict.update(kw['env'])
350 if kw.has_key('target'):
353 if not type(t) is types.ListType:
355 dict['TARGETS'] = PathList(map(os.path.normpath, map(str, t)))
357 dict['TARGET'] = dict['TARGETS'][0]
358 if kw.has_key('source'):
361 if not type(s) is types.ListType:
363 dict['SOURCES'] = PathList(map(os.path.normpath, map(str, s)))
367 # Autogenerate necessary construction variables.
372 class CommandAction(ActionBase):
373 """Class for command-execution actions."""
374 def __init__(self, string):
375 self.command = string
377 def execute(self, **kw):
378 dict = apply(self.subst_dict, (), kw)
379 cmd_list = scons_subst_list(self.command, dict, {})
380 for cmd_line in cmd_list:
383 self.show(string.join(cmd_line))
386 ENV = kw['env']['ENV']
388 import SCons.Defaults
389 ENV = SCons.Defaults.ConstructionEnvironment['ENV']
390 ret = spawn(cmd_line[0], cmd_line, ENV)
395 def get_contents(self, **kw):
396 """Return the signature contents of this action's command line.
398 For signature purposes, it doesn't matter what targets or
399 sources we use, so long as we use the same ones every time
400 so the signature stays the same. We supply an array of two
401 of each to allow for distinction between TARGET and TARGETS.
403 kw['target'] = ['__t1__', '__t2__']
404 kw['source'] = ['__s1__', '__s2__']
405 dict = apply(self.subst_dict, (), kw)
406 return scons_subst(self.command, dict, {})
408 class FunctionAction(ActionBase):
409 """Class for Python function actions."""
410 def __init__(self, function):
411 self.function = function
413 def execute(self, **kw):
415 # XXX: WHAT SHOULD WE PRINT HERE?
417 if kw.has_key('target'):
418 if type(kw['target']) is types.ListType:
419 kw['target'] = map(str, kw['target'])
421 kw['target'] = str(kw['target'])
422 if kw.has_key('source'):
423 kw['source'] = map(str, kw['source'])
424 return apply(self.function, (), kw)
426 def get_contents(self, **kw):
427 """Return the signature contents of this callable action.
429 By providing direct access to the code object of the
430 function, Python makes this extremely easy. Hooray!
432 #XXX DOES NOT ACCOUNT FOR CHANGES IN ENVIRONMENT VARIABLES
433 #THE FUNCTION MAY USE
435 # "self.function" is a function.
436 code = self.function.func_code.co_code
438 # "self.function" is a callable object.
439 code = self.function.__call__.im_func.func_code.co_code
442 class ListAction(ActionBase):
443 """Class for lists of other actions."""
444 def __init__(self, list):
445 self.list = map(lambda x: Action(x), list)
447 def execute(self, **kw):
449 r = apply(l.execute, (), kw)
454 def get_contents(self, **kw):
455 """Return the signature contents of this action list.
457 Simple concatenation of the signatures of the elements.
460 return reduce(lambda x, y: x + str(y.get_contents()), self.list, "")