Move autogeneration of PATH-based variables from Environment initialization to variab...
[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
35 import os.path
36 import SCons.Node.FS
37 from SCons.Util import PathList, scons_str2nodes, scons_subst, scons_subst_list, autogenerate
38 import string
39 import types
40 from UserList import UserList
41 from UserDict import UserDict
42 from Errors import UserError
43
44 try:
45     from UserString import UserString
46 except ImportError:
47     class UserString:
48         pass
49
50
51
52 if os.name == 'posix':
53
54     def spawn(cmd, args, env):
55         pid = os.fork()
56         if not pid:
57             # Child process.
58             os.execvpe(cmd, args, env)
59         else:
60             # Parent process.
61             pid, stat = os.waitpid(pid, 0)
62             ret = stat >> 8
63             return ret
64
65 elif os.name == 'nt':
66
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):
72                 exts = env['PATHEXT']
73                 if type(exts) != type([]):
74                     exts = string.split(exts, os.pathsep)
75                 for e in exts:
76                     f = cmd + e
77                     if os.path.exists(f):
78                         return f
79         else:
80             path = env['PATH']
81             if type(path) != type([]):
82                 path = string.split(path, os.pathsep)
83             exts = env['PATHEXT']
84             if type(exts) != type([]):
85                 exts = string.split(exts, os.pathsep)
86             pairs = []
87             for dir in path:
88                 for e in exts:
89                     pairs.append((dir, e))
90             for dir, ext in pairs:
91                 f = os.path.join(dir, cmd)
92                 if not ext is None:
93                     f = f + ext
94                 if os.path.exists(f):
95                     return f
96         return cmd
97
98     def spawn(cmd, args, env):
99         try:
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)
104         return ret
105
106
107
108 def Builder(**kw):
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']
115         builders = []
116         for suffix, action in action_dict.items():
117             bld_kw = kw.copy()
118             bld_kw['action'] = action
119             bld_kw['src_suffix'] = suffix
120             builders.append(apply(BuilderBase, (), bld_kw))
121         del kw['action']
122         kw['builders'] = builders
123         return apply(CompositeBuilder, (), kw)
124     else:
125         return apply(BuilderBase, (), kw)
126
127
128
129 class BuilderBase:
130     """Base class for Builders, objects that create output
131     nodes (files) from input nodes (files).
132     """
133
134     def __init__(self,  name = None,
135                         action = None,
136                         prefix = '',
137                         suffix = '',
138                         src_suffix = '',
139                         node_factory = SCons.Node.FS.default_fs.File,
140                         scanner = None):
141         self.name = name
142         self.action = Action(action)
143
144         self.prefix = prefix
145         self.suffix = suffix
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
153
154     def __cmp__(self, other):
155         return cmp(self.__dict__, other.__dict__)
156
157     def __call__(self, env, target = None, source = None):
158         def adjustixes(files, pre, suf):
159             ret = []
160             if not type(files) is type([]):
161                 files = [files]
162             for f in files:
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)
167                     if suf:
168                         if f[-len(suf):] != suf:
169                             f = f + suf
170                 ret.append(f)
171             return ret
172
173         tlist = scons_str2nodes(adjustixes(target,
174                                            env.subst(self.prefix),
175                                            env.subst(self.suffix)),
176                                 self.node_factory)
177
178         slist = scons_str2nodes(adjustixes(source, None,
179                                            env.subst(self.src_suffix)),
180                                 self.node_factory)
181
182         for t in tlist:
183             t.builder_set(self)
184             t.env_set(env)
185             t.add_source(slist)
186             if self.scanner:
187                 t.scanner_set(self.scanner.instance(env))
188
189         for s in slist:
190             s.env_set(env, 1)
191             scanner = env.get_scanner(os.path.splitext(s.name)[1])
192             if scanner:
193                 s.scanner_set(scanner.instance(env))
194
195         if len(tlist) == 1:
196             tlist = tlist[0]
197         return tlist
198
199     def execute(self, **kw):
200         """Execute a builder's action to create an output object.
201         """
202         return apply(self.action.execute, (), kw)
203
204     def get_contents(self, **kw):
205         """Fetch the "contents" of the builder's action
206         (for signature calculation).
207         """
208         return apply(self.action.get_contents, (), kw)
209
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.
216
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
219     src_suffix.
220     """
221     def __init__(self,  src_builder,
222                         name = None,
223                         action = None,
224                         prefix = '',
225                         suffix = '',
226                         src_suffix = '',
227                         node_factory = SCons.Node.FS.default_fs.File,
228                         scanner=None):
229         BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix,
230                              node_factory, scanner)
231         self.src_builder = src_builder
232
233     def __call__(self, env, target = None, source = None):
234         slist = scons_str2nodes(source, self.node_factory)
235         final_sources = []
236         src_suffix = env.subst(self.src_suffix)
237         for snode in slist:
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 ],
241                                      source=snode)
242                 if not type(tgt) is types.ListType:
243                     final_sources.append(tgt)
244                 else:
245                     final_sources.extend(tgt)
246             else:
247                 final_sources.append(snode)
248         return BuilderBase.__call__(self, env, target=target,
249                                     source=final_sources)
250
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,
257     UserError is thrown.
258
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,
262                         prefix='',
263                         suffix='',
264                         builders=[]):
265         BuilderBase.__init__(self, name=name, prefix=prefix,
266                              suffix=suffix)
267         self.builder_dict = {}
268         for bld in builders:
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
272
273     def __call__(self, env, target = None, source = None):
274         ret = BuilderBase.__call__(self, env, target=target, source=source)
275
276         builder_dict = {}
277         for suffix, bld in self.builder_dict.items():
278             builder_dict[env.subst(bld.src_suffix)] = bld
279
280         if type(ret) is types.ListType:
281             tlist = ret
282         else:
283             tlist = [ ret ]
284         for tnode in tlist:
285             suflist = map(lambda x: os.path.splitext(x.path)[1],
286                           tnode.sources)
287             last_suffix=''
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
291                 last_suffix = suffix
292             if last_suffix:
293                 try:
294                     tnode.builder_set(builder_dict[last_suffix])
295                 except KeyError:
296                     raise UserError, "Builder not capable of building files with suffix: %s" % suffix
297         return ret
298
299 print_actions = 1;
300 execute_actions = 1;
301
302 def Action(act):
303     """A factory for action objects."""
304     if callable(act):
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)
310     else:
311         return None
312     
313 class ActionBase:
314     """Base class for actions that create output objects.
315     
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__)
320
321     def show(self, string):
322         print string
323
324     def subst_dict(self, **kw):
325         """Create a dictionary for substitution of construction
326         variables.
327
328         This translates the following special arguments:
329
330             env    - the construction environment itself,
331                      the values of which (CC, CCFLAGS, etc.)
332                      are copied straight into the dictionary
333
334             target - the target (object or array of objects),
335                      used to generate the TARGET and TARGETS
336                      construction variables
337
338             source - the source (object or array of objects),
339                      used to generate the SOURCES construction
340                      variable
341
342         Any other keyword arguments are copied into the
343         dictionary."""
344
345         dict = {}
346         if kw.has_key('env'):
347             dict.update(kw['env'])
348             del kw['env']
349
350         if kw.has_key('target'):
351             t = kw['target']
352             del kw['target']
353             if not type(t) is types.ListType:
354                 t = [t]
355             dict['TARGETS'] = PathList(map(os.path.normpath, map(str, t)))
356             if dict['TARGETS']:
357                 dict['TARGET'] = dict['TARGETS'][0]
358         if kw.has_key('source'):
359             s = kw['source']
360             del kw['source']
361             if not type(s) is types.ListType:
362                 s = [s]
363             dict['SOURCES'] = PathList(map(os.path.normpath, map(str, s)))
364
365         dict.update(kw)
366
367         # Autogenerate necessary construction variables.
368         autogenerate(dict)
369
370         return dict
371
372 class CommandAction(ActionBase):
373     """Class for command-execution actions."""
374     def __init__(self, string):
375         self.command = string
376
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:
381             if len(cmd_line):
382                 if print_actions:
383                     self.show(string.join(cmd_line))
384                 if execute_actions:
385                     try:
386                         ENV = kw['env']['ENV']
387                     except:
388                         import SCons.Defaults
389                         ENV = SCons.Defaults.ConstructionEnvironment['ENV']
390                     ret = spawn(cmd_line[0], cmd_line, ENV)
391                     if ret:
392                         return ret
393         return 0
394
395     def get_contents(self, **kw):
396         """Return the signature contents of this action's command line.
397
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.
402         """
403         kw['target'] = ['__t1__', '__t2__']
404         kw['source'] = ['__s1__', '__s2__']
405         dict = apply(self.subst_dict, (), kw)
406         return scons_subst(self.command, dict, {})
407
408 class FunctionAction(ActionBase):
409     """Class for Python function actions."""
410     def __init__(self, function):
411         self.function = function
412
413     def execute(self, **kw):
414         # if print_actions:
415         # XXX:  WHAT SHOULD WE PRINT HERE?
416         if execute_actions:
417             if kw.has_key('target'):
418                 if type(kw['target']) is types.ListType:
419                     kw['target'] = map(str, kw['target'])
420                 else:
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)
425
426     def get_contents(self, **kw):
427         """Return the signature contents of this callable action.
428
429         By providing direct access to the code object of the
430         function, Python makes this extremely easy.  Hooray!
431         """
432         #XXX DOES NOT ACCOUNT FOR CHANGES IN ENVIRONMENT VARIABLES
433         #THE FUNCTION MAY USE
434         try:
435             # "self.function" is a function.
436             code = self.function.func_code.co_code
437         except:
438             # "self.function" is a callable object.
439             code = self.function.__call__.im_func.func_code.co_code
440         return str(code)
441
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)
446
447     def execute(self, **kw):
448         for l in self.list:
449             r = apply(l.execute, (), kw)
450             if r != 0:
451                 return r
452         return 0
453
454     def get_contents(self, **kw):
455         """Return the signature contents of this action list.
456
457         Simple concatenation of the signatures of the elements.
458         """
459
460         return reduce(lambda x, y: x + str(y.get_contents()), self.list, "")