Merged revisions 1968-2115 via svnmerge from
[scons.git] / src / engine / SCons / Environment.py
1 """SCons.Environment
2
3 Base class for construction Environments.  These are
4 the primary objects used to communicate dependency and
5 construction information to the build engine.
6
7 Keyword arguments supplied when the construction Environment
8 is created are construction variables used to initialize the
9 Environment 
10 """
11
12 #
13 # __COPYRIGHT__
14 #
15 # Permission is hereby granted, free of charge, to any person obtaining
16 # a copy of this software and associated documentation files (the
17 # "Software"), to deal in the Software without restriction, including
18 # without limitation the rights to use, copy, modify, merge, publish,
19 # distribute, sublicense, and/or sell copies of the Software, and to
20 # permit persons to whom the Software is furnished to do so, subject to
21 # the following conditions:
22 #
23 # The above copyright notice and this permission notice shall be included
24 # in all copies or substantial portions of the Software.
25 #
26 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
27 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
28 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 #
34
35 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
36
37
38 import copy
39 import os
40 import os.path
41 import string
42 from UserDict import UserDict
43
44 import SCons.Action
45 import SCons.Builder
46 from SCons.Debug import logInstanceCreation
47 import SCons.Defaults
48 import SCons.Errors
49 import SCons.Memoize
50 import SCons.Node
51 import SCons.Node.Alias
52 import SCons.Node.FS
53 import SCons.Node.Python
54 import SCons.Platform
55 import SCons.SConsign
56 import SCons.Sig
57 import SCons.Subst
58 import SCons.Tool
59 import SCons.Util
60 import SCons.Warnings
61
62 class _Null:
63     pass
64
65 _null = _Null
66
67 CleanTargets = {}
68 CalculatorArgs = {}
69
70 # Pull UserError into the global name space for the benefit of
71 # Environment().SourceSignatures(), which has some import statements
72 # which seem to mess up its ability to reference SCons directly.
73 UserError = SCons.Errors.UserError
74
75 def installFunc(target, source, env):
76     """Install a source file into a target using the function specified
77     as the INSTALL construction variable."""
78     try:
79         install = env['INSTALL']
80     except KeyError:
81         raise SCons.Errors.UserError('Missing INSTALL construction variable.')
82     return install(target[0].path, source[0].path, env)
83
84 def installString(target, source, env):
85     s = env.get('INSTALLSTR', '')
86     if callable(s):
87         return s(target[0].path, source[0].path, env)
88     else:
89         return env.subst_target_source(s, 0, target, source)
90
91 installAction = SCons.Action.Action(installFunc, installString)
92
93 InstallBuilder = SCons.Builder.Builder(action=installAction,
94                                        name='InstallBuilder')
95
96 def alias_builder(env, target, source):
97     pass
98
99 AliasBuilder = SCons.Builder.Builder(action = alias_builder,
100                                      target_factory = SCons.Node.Alias.default_ans.Alias,
101                                      source_factory = SCons.Node.FS.Entry,
102                                      multi = 1,
103                                      is_explicit = None,
104                                      name='AliasBuilder')
105
106 def our_deepcopy(x):
107    """deepcopy lists and dictionaries, and just copy the reference
108    for everything else."""
109    if SCons.Util.is_Dict(x):
110        copy = {}
111        for key in x.keys():
112            copy[key] = our_deepcopy(x[key])
113    elif SCons.Util.is_List(x):
114        copy = map(our_deepcopy, x)
115        try:
116            copy = x.__class__(copy)
117        except AttributeError:
118            pass
119    else:
120        copy = x
121    return copy
122
123 def apply_tools(env, tools, toolpath):
124     # Store the toolpath in the Environment.
125     if toolpath is not None:
126         env['toolpath'] = toolpath
127
128     if not tools:
129         return
130     # Filter out null tools from the list.
131     for tool in filter(None, tools):
132         if SCons.Util.is_List(tool) or type(tool)==type(()):
133             toolname = tool[0]
134             toolargs = tool[1] # should be a dict of kw args
135             tool = apply(env.Tool, [toolname], toolargs)
136         else:
137             env.Tool(tool)
138
139 # These names are controlled by SCons; users should never set or override
140 # them.  This warning can optionally be turned off, but scons will still
141 # ignore the illegal variable names even if it's off.
142 reserved_construction_var_names = \
143     ['TARGET', 'TARGETS', 'SOURCE', 'SOURCES']
144
145 def copy_non_reserved_keywords(dict):
146     result = our_deepcopy(dict)
147     for k in result.keys():
148         if k in reserved_construction_var_names:
149             SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning,
150                                 "Ignoring attempt to set reserved variable `%s'" % k)
151             del result[k]
152     return result
153
154 def _set_reserved(env, key, value):
155     msg = "Ignoring attempt to set reserved variable `%s'" % key
156     SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning, msg)
157
158 def _set_BUILDERS(env, key, value):
159     try:
160         bd = env._dict[key]
161         for k in bd.keys():
162             del bd[k]
163     except KeyError:
164         env._dict[key] = BuilderDict(kwbd, env)
165     env._dict[key].update(value)
166
167 def _del_SCANNERS(env, key):
168     del env._dict[key]
169     env.scanner_map_delete()
170
171 def _set_SCANNERS(env, key, value):
172     env._dict[key] = value
173     env.scanner_map_delete()
174
175 class BuilderWrapper:
176     """Wrapper class that associates an environment with a Builder at
177     instantiation."""
178     def __init__(self, env, builder):
179         self.env = env
180         self.builder = builder
181
182     def __call__(self, target=None, source=_null, *args, **kw):
183         if source is _null:
184             source = target
185             target = None
186         if not target is None and not SCons.Util.is_List(target):
187             target = [target]
188         if not source is None and not SCons.Util.is_List(source):
189             source = [source]
190         return apply(self.builder, (self.env, target, source) + args, kw)
191
192     # This allows a Builder to be executed directly
193     # through the Environment to which it's attached.
194     # In practice, we shouldn't need this, because
195     # builders actually get executed through a Node.
196     # But we do have a unit test for this, and can't
197     # yet rule out that it would be useful in the
198     # future, so leave it for now.
199     def execute(self, **kw):
200         kw['env'] = self.env
201         apply(self.builder.execute, (), kw)
202
203 class BuilderDict(UserDict):
204     """This is a dictionary-like class used by an Environment to hold
205     the Builders.  We need to do this because every time someone changes
206     the Builders in the Environment's BUILDERS dictionary, we must
207     update the Environment's attributes."""
208     def __init__(self, dict, env):
209         # Set self.env before calling the superclass initialization,
210         # because it will end up calling our other methods, which will
211         # need to point the values in this dictionary to self.env.
212         self.env = env
213         UserDict.__init__(self, dict)
214
215     def __setitem__(self, item, val):
216         UserDict.__setitem__(self, item, val)
217         try:
218             self.setenvattr(item, val)
219         except AttributeError:
220             # Have to catch this because sometimes __setitem__ gets
221             # called out of __init__, when we don't have an env
222             # attribute yet, nor do we want one!
223             pass
224
225     def setenvattr(self, item, val):
226         """Set the corresponding environment attribute for this Builder.
227
228         If the value is already a BuilderWrapper, we pull the builder
229         out of it and make another one, so that making a copy of an
230         existing BuilderDict is guaranteed separate wrappers for each
231         Builder + Environment pair."""
232         try:
233             builder = val.builder
234         except AttributeError:
235             builder = val
236         setattr(self.env, item, BuilderWrapper(self.env, builder))
237
238     def __delitem__(self, item):
239         UserDict.__delitem__(self, item)
240         delattr(self.env, item)
241
242     def update(self, dict):
243         for i, v in dict.items():
244             self.__setitem__(i, v)
245
246 class SubstitutionEnvironment:
247     """Base class for different flavors of construction environments.
248
249     This class contains a minimal set of methods that handle contruction
250     variable expansion and conversion of strings to Nodes, which may or
251     may not be actually useful as a stand-alone class.  Which methods
252     ended up in this class is pretty arbitrary right now.  They're
253     basically the ones which we've empirically determined are common to
254     the different construction environment subclasses, and most of the
255     others that use or touch the underlying dictionary of construction
256     variables.
257
258     Eventually, this class should contain all the methods that we
259     determine are necessary for a "minimal" interface to the build engine.
260     A full "native Python" SCons environment has gotten pretty heavyweight
261     with all of the methods and Tools and construction variables we've
262     jammed in there, so it would be nice to have a lighter weight
263     alternative for interfaces that don't need all of the bells and
264     whistles.  (At some point, we'll also probably rename this class
265     "Base," since that more reflects what we want this class to become,
266     but because we've released comments that tell people to subclass
267     Environment.Base to create their own flavors of construction
268     environment, we'll save that for a future refactoring when this
269     class actually becomes useful.)
270     """
271
272     if SCons.Memoize.use_memoizer:
273         __metaclass__ = SCons.Memoize.Memoized_Metaclass
274
275     def __init__(self, **kw):
276         """Initialization of an underlying SubstitutionEnvironment class.
277         """
278         if __debug__: logInstanceCreation(self, 'Environment.SubstitutionEnvironment')
279         self.fs = SCons.Node.FS.default_fs or SCons.Node.FS.FS()
280         self.ans = SCons.Node.Alias.default_ans
281         self.lookup_list = SCons.Node.arg2nodes_lookups
282         self._dict = kw.copy()
283         self._init_special()
284         #self._memo = {}
285
286     def _init_special(self):
287         """Initial the dispatch tables for special handling of
288         special construction variables."""
289         self._special_del = {}
290         self._special_del['SCANNERS'] = _del_SCANNERS
291
292         self._special_set = {}
293         for key in reserved_construction_var_names:
294             self._special_set[key] = _set_reserved
295         self._special_set['BUILDERS'] = _set_BUILDERS
296         self._special_set['SCANNERS'] = _set_SCANNERS
297
298     def __cmp__(self, other):
299         return cmp(self._dict, other._dict)
300
301     def __delitem__(self, key):
302         special = self._special_del.get(key)
303         if special:
304             special(self, key)
305         else:
306             del self._dict[key]
307
308     def __getitem__(self, key):
309         return self._dict[key]
310
311     def __setitem__(self, key, value):
312         special = self._special_set.get(key)
313         if special:
314             special(self, key, value)
315         else:
316             if not SCons.Util.is_valid_construction_var(key):
317                 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
318             self._dict[key] = value
319
320     def get(self, key, default=None):
321         "Emulates the get() method of dictionaries."""
322         return self._dict.get(key, default)
323
324     def has_key(self, key):
325         return self._dict.has_key(key)
326
327     def items(self):
328         return self._dict.items()
329
330     def arg2nodes(self, args, node_factory=_null, lookup_list=_null, **kw):
331         if node_factory is _null:
332             node_factory = self.fs.File
333         if lookup_list is _null:
334             lookup_list = self.lookup_list
335
336         if not args:
337             return []
338
339         if SCons.Util.is_List(args):
340             args = SCons.Util.flatten(args)
341         else:
342             args = [args]
343
344         nodes = []
345         for v in args:
346             if SCons.Util.is_String(v):
347                 n = None
348                 for l in lookup_list:
349                     n = l(v)
350                     if not n is None:
351                         break
352                 if not n is None:
353                     if SCons.Util.is_String(n):
354                         # n = self.subst(n, raw=1, **kw)
355                         kw['raw'] = 1
356                         n = apply(self.subst, (n,), kw)
357                         if node_factory:
358                             n = node_factory(n)
359                     if SCons.Util.is_List(n):
360                         nodes.extend(n)
361                     else:
362                         nodes.append(n)
363                 elif node_factory:
364                     # v = node_factory(self.subst(v, raw=1, **kw))
365                     kw['raw'] = 1
366                     v = node_factory(apply(self.subst, (v,), kw))
367                     if SCons.Util.is_List(v):
368                         nodes.extend(v)
369                     else:
370                         nodes.append(v)
371             else:
372                 nodes.append(v)
373     
374         return nodes
375
376     def gvars(self):
377         return self._dict
378
379     def lvars(self):
380         return {}
381
382     def subst(self, string, raw=0, target=None, source=None, conv=None):
383         """Recursively interpolates construction variables from the
384         Environment into the specified string, returning the expanded
385         result.  Construction variables are specified by a $ prefix
386         in the string and begin with an initial underscore or
387         alphabetic character followed by any number of underscores
388         or alphanumeric characters.  The construction variable names
389         may be surrounded by curly braces to separate the name from
390         trailing characters.
391         """
392         gvars = self.gvars()
393         lvars = self.lvars()
394         lvars['__env__'] = self
395         return SCons.Subst.scons_subst(string, self, raw, target, source, gvars, lvars, conv)
396
397     def subst_kw(self, kw, raw=0, target=None, source=None):
398         nkw = {}
399         for k, v in kw.items():
400             k = self.subst(k, raw, target, source)
401             if SCons.Util.is_String(v):
402                 v = self.subst(v, raw, target, source)
403             nkw[k] = v
404         return nkw
405
406     def subst_list(self, string, raw=0, target=None, source=None, conv=None):
407         """Calls through to SCons.Subst.scons_subst_list().  See
408         the documentation for that function."""
409         gvars = self.gvars()
410         lvars = self.lvars()
411         lvars['__env__'] = self
412         return SCons.Subst.scons_subst_list(string, self, raw, target, source, gvars, lvars, conv)
413
414     def subst_path(self, path, target=None, source=None):
415         """Substitute a path list, turning EntryProxies into Nodes
416         and leaving Nodes (and other objects) as-is."""
417
418         if not SCons.Util.is_List(path):
419             path = [path]
420
421         def s(obj):
422             """This is the "string conversion" routine that we have our
423             substitutions use to return Nodes, not strings.  This relies
424             on the fact that an EntryProxy object has a get() method that
425             returns the underlying Node that it wraps, which is a bit of
426             architectural dependence that we might need to break or modify
427             in the future in response to additional requirements."""
428             try:
429                 get = obj.get
430             except AttributeError:
431                 pass
432             else:
433                 obj = get()
434             return obj
435
436         r = []
437         for p in path:
438             if SCons.Util.is_String(p):
439                 p = self.subst(p, target=target, source=source, conv=s)
440                 if SCons.Util.is_List(p):
441                     if len(p) == 1:
442                         p = p[0]
443                     else:
444                         # We have an object plus a string, or multiple
445                         # objects that we need to smush together.  No choice
446                         # but to make them into a string.
447                         p = string.join(map(SCons.Util.to_String, p), '')
448             else:
449                 p = s(p)
450             r.append(p)
451         return r
452
453     subst_target_source = subst
454
455     def backtick(self, command):
456         import subprocess
457         if SCons.Util.is_List(command): 
458             p = subprocess.Popen(command,
459                                  stdout=subprocess.PIPE,
460                                  stderr=subprocess.PIPE,
461                                  universal_newlines=True)
462         else:
463             p = subprocess.Popen(command,
464                                  stdout=subprocess.PIPE,
465                                  stderr=subprocess.PIPE,
466                                  universal_newlines=True,
467                                  shell=True)
468         out = p.stdout.read()
469         p.stdout.close()
470         err = p.stderr.read()
471         p.stderr.close()
472         status = p.wait()
473         if err:
474             import sys
475             sys.stderr.write(err)
476         if status:
477             raise OSError("'%s' exited %d" % (command, status))
478         return out
479
480     def AddMethod(self, function, name=None):
481         """
482         Adds the specified function as a method of this construction
483         environment with the specified name.  If the name is omitted,
484         the default name is the name of the function itself.
485         """
486         SCons.Util.AddMethod(self, function, name)
487
488     def Override(self, overrides):
489         """
490         Produce a modified environment whose variables are overriden by
491         the overrides dictionaries.  "overrides" is a dictionary that
492         will override the variables of this environment.
493
494         This function is much more efficient than Clone() or creating
495         a new Environment because it doesn't copy the construction
496         environment dictionary, it just wraps the underlying construction
497         environment, and doesn't even create a wrapper object if there
498         are no overrides.
499         """
500         if overrides:
501             o = copy_non_reserved_keywords(overrides)
502             overrides = {}
503             for key, value in o.items():
504                 overrides[key] = SCons.Subst.scons_subst_once(value, self, key)
505         if overrides:
506             env = OverrideEnvironment(self, overrides)
507             return env
508         else:
509             return self
510
511     def ParseFlags(self, *flags):
512         """
513         Parse the set of flags and return a dict with the flags placed
514         in the appropriate entry.  The flags are treated as a typical
515         set of command-line flags for a GNU-like toolchain and used to
516         populate the entries in the dict immediately below.  If one of
517         the flag strings begins with a bang (exclamation mark), it is
518         assumed to be a command and the rest of the string is executed;
519         the result of that evaluation is then added to the dict.
520         """
521         dict = {
522             'ASFLAGS'       : SCons.Util.CLVar(''),
523             'CFLAGS'        : SCons.Util.CLVar(''),
524             'CCFLAGS'       : SCons.Util.CLVar(''),
525             'CPPDEFINES'    : [],
526             'CPPFLAGS'      : SCons.Util.CLVar(''),
527             'CPPPATH'       : [],
528             'FRAMEWORKPATH' : SCons.Util.CLVar(''),
529             'FRAMEWORKS'    : SCons.Util.CLVar(''),
530             'LIBPATH'       : [],
531             'LIBS'          : [],
532             'LINKFLAGS'     : SCons.Util.CLVar(''),
533             'RPATH'         : [],
534         }
535
536         # The use of the "me" parameter to provide our own name for
537         # recursion is an egregious hack to support Python 2.1 and before.
538         def do_parse(arg, me, self = self, dict = dict):
539             # if arg is a sequence, recurse with each element
540             if not arg:
541                 return
542
543             if not SCons.Util.is_String(arg):
544                 for t in arg: me(t, me)
545                 return
546
547             # if arg is a command, execute it
548             if arg[0] == '!':
549                 arg = self.backtick(arg[1:])
550
551             # utility function to deal with -D option
552             def append_define(name, dict = dict):
553                 t = string.split(name, '=')
554                 if len(t) == 1:
555                     dict['CPPDEFINES'].append(name)
556                 else:
557                     dict['CPPDEFINES'].append([t[0], string.join(t[1:], '=')])
558
559             # Loop through the flags and add them to the appropriate option.
560             # This tries to strike a balance between checking for all possible
561             # flags and keeping the logic to a finite size, so it doesn't
562             # check for some that don't occur often.  It particular, if the
563             # flag is not known to occur in a config script and there's a way
564             # of passing the flag to the right place (by wrapping it in a -W
565             # flag, for example) we don't check for it.  Note that most
566             # preprocessor options are not handled, since unhandled options
567             # are placed in CCFLAGS, so unless the preprocessor is invoked
568             # separately, these flags will still get to the preprocessor.
569             # Other options not currently handled:
570             #  -iqoutedir      (preprocessor search path)
571             #  -u symbol       (linker undefined symbol)
572             #  -s              (linker strip files)
573             #  -static*        (linker static binding)
574             #  -shared*        (linker dynamic binding)
575             #  -symbolic       (linker global binding)
576             #  -R dir          (deprecated linker rpath)
577             # IBM compilers may also accept -qframeworkdir=foo
578     
579             params = string.split(arg)
580             append_next_arg_to = None   # for multi-word args
581             for arg in params:
582                 if append_next_arg_to:
583                    if append_next_arg_to == 'CPPDEFINES':
584                        append_define(arg)
585                    elif append_next_arg_to == '-include':
586                        t = ('-include', self.fs.File(arg))
587                        dict['CCFLAGS'].append(t)
588                    elif append_next_arg_to == '-isysroot':
589                        t = ('-isysroot', arg)
590                        dict['CCFLAGS'].append(t)
591                        dict['LINKFLAGS'].append(t)
592                    elif append_next_arg_to == '-arch':
593                        t = ('-arch', arg)
594                        dict['CCFLAGS'].append(t)
595                        dict['LINKFLAGS'].append(t)
596                    else:
597                        dict[append_next_arg_to].append(arg)
598                    append_next_arg_to = None
599                 elif not arg[0] in ['-', '+']:
600                     dict['LIBS'].append(self.fs.File(arg))
601                 elif arg[:2] == '-L':
602                     if arg[2:]:
603                         dict['LIBPATH'].append(arg[2:])
604                     else:
605                         append_next_arg_to = 'LIBPATH'
606                 elif arg[:2] == '-l':
607                     if arg[2:]:
608                         dict['LIBS'].append(arg[2:])
609                     else:
610                         append_next_arg_to = 'LIBS'
611                 elif arg[:2] == '-I':
612                     if arg[2:]:
613                         dict['CPPPATH'].append(arg[2:])
614                     else:
615                         append_next_arg_to = 'CPPPATH'
616                 elif arg[:4] == '-Wa,':
617                     dict['ASFLAGS'].append(arg[4:])
618                     dict['CCFLAGS'].append(arg)
619                 elif arg[:4] == '-Wl,':
620                     if arg[:11] == '-Wl,-rpath=':
621                         dict['RPATH'].append(arg[11:])
622                     elif arg[:7] == '-Wl,-R,':
623                         dict['RPATH'].append(arg[7:])
624                     elif arg[:6] == '-Wl,-R':
625                         dict['RPATH'].append(arg[6:])
626                     else:
627                         dict['LINKFLAGS'].append(arg)
628                 elif arg[:4] == '-Wp,':
629                     dict['CPPFLAGS'].append(arg)
630                 elif arg[:2] == '-D':
631                     if arg[2:]:
632                         append_define(arg[2:])
633                     else:
634                         append_next_arg_to = 'CPPDEFINES'
635                 elif arg == '-framework':
636                     append_next_arg_to = 'FRAMEWORKS'
637                 elif arg[:14] == '-frameworkdir=':
638                     dict['FRAMEWORKPATH'].append(arg[14:])
639                 elif arg[:2] == '-F':
640                     if arg[2:]:
641                         dict['FRAMEWORKPATH'].append(arg[2:])
642                     else:
643                         append_next_arg_to = 'FRAMEWORKPATH'
644                 elif arg == '-mno-cygwin':
645                     dict['CCFLAGS'].append(arg)
646                     dict['LINKFLAGS'].append(arg)
647                 elif arg == '-mwindows':
648                     dict['LINKFLAGS'].append(arg)
649                 elif arg == '-pthread':
650                     dict['CCFLAGS'].append(arg)
651                     dict['LINKFLAGS'].append(arg)
652                 elif arg[:5] == '-std=':
653                     dict['CFLAGS'].append(arg) # C only
654                 elif arg[0] == '+':
655                     dict['CCFLAGS'].append(arg)
656                     dict['LINKFLAGS'].append(arg)
657                 elif arg in ['-include', '-isysroot', '-arch']:
658                     append_next_arg_to = arg
659                 else:
660                     dict['CCFLAGS'].append(arg)
661     
662         for arg in flags:
663             do_parse(arg, do_parse)
664         return dict
665
666     def MergeFlags(self, args, unique=1):
667         """
668         Merge the dict in args into the construction variables.  If args
669         is not a dict, it is converted into a dict using ParseFlags.
670         If unique is not set, the flags are appended rather than merged.
671         """
672
673         if not SCons.Util.is_Dict(args):
674             args = self.ParseFlags(args)
675         if not unique:
676             apply(self.Append, (), args)
677             return self
678         for key, value in args.items():
679             if not value:
680                 continue
681             try:
682                 orig = self[key]
683             except KeyError:
684                 orig = value
685             else:
686                 if not orig:
687                     orig = value
688                 elif value:
689                     # Add orig and value.  The logic here was lifted from
690                     # part of env.Append() (see there for a lot of comments
691                     # about the order in which things are tried) and is
692                     # used mainly to handle coercion of strings to CLVar to
693                     # "do the right thing" given (e.g.) an original CCFLAGS
694                     # string variable like '-pipe -Wall'.
695                     try:
696                         orig = orig + value
697                     except (KeyError, TypeError):
698                         try:
699                             add_to_orig = orig.append
700                         except AttributeError:
701                             value.insert(0, orig)
702                             orig = value
703                         else:
704                             add_to_orig(value)
705             t = []
706             if key[-4:] == 'PATH':
707                 ### keep left-most occurence
708                 for v in orig:
709                     if v not in t:
710                         t.append(v)
711             else:
712                 ### keep right-most occurence
713                 orig.reverse()
714                 for v in orig:
715                     if v not in t:
716                         t.insert(0, v)
717             self[key] = t
718         return self
719
720 class Base(SubstitutionEnvironment):
721     """Base class for "real" construction Environments.  These are the
722     primary objects used to communicate dependency and construction
723     information to the build engine.
724
725     Keyword arguments supplied when the construction Environment
726     is created are construction variables used to initialize the
727     Environment.
728     """
729
730     if SCons.Memoize.use_memoizer:
731         __metaclass__ = SCons.Memoize.Memoized_Metaclass
732
733     memoizer_counters = []
734
735     #######################################################################
736     # This is THE class for interacting with the SCons build engine,
737     # and it contains a lot of stuff, so we're going to try to keep this
738     # a little organized by grouping the methods.
739     #######################################################################
740
741     #######################################################################
742     # Methods that make an Environment act like a dictionary.  These have
743     # the expected standard names for Python mapping objects.  Note that
744     # we don't actually make an Environment a subclass of UserDict for
745     # performance reasons.  Note also that we only supply methods for
746     # dictionary functionality that we actually need and use.
747     #######################################################################
748
749     def __init__(self,
750                  platform=None,
751                  tools=None,
752                  toolpath=None,
753                  options=None,
754                  **kw):
755         """
756         Initialization of a basic SCons construction environment,
757         including setting up special construction variables like BUILDER,
758         PLATFORM, etc., and searching for and applying available Tools.
759
760         Note that we do *not* call the underlying base class
761         (SubsitutionEnvironment) initialization, because we need to
762         initialize things in a very specific order that doesn't work
763         with the much simpler base class initialization.
764         """
765         if __debug__: logInstanceCreation(self, 'Environment.Base')
766         self._memo = {}
767         self.fs = SCons.Node.FS.default_fs or SCons.Node.FS.FS()
768         self.ans = SCons.Node.Alias.default_ans
769         self.lookup_list = SCons.Node.arg2nodes_lookups
770         self._dict = our_deepcopy(SCons.Defaults.ConstructionEnvironment)
771         self._init_special()
772
773         self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
774
775         if platform is None:
776             platform = self._dict.get('PLATFORM', None)
777             if platform is None:
778                 platform = SCons.Platform.Platform()
779         if SCons.Util.is_String(platform):
780             platform = SCons.Platform.Platform(platform)
781         self._dict['PLATFORM'] = str(platform)
782         platform(self)
783
784         # Apply the passed-in variables and customizable options to the
785         # environment before calling the tools, because they may use
786         # some of them during initialization.
787         apply(self.Replace, (), kw)
788         keys = kw.keys()
789         if options:
790             keys = keys + options.keys()
791             options.Update(self)
792
793         save = {}
794         for k in keys:
795             try:
796                 save[k] = self._dict[k]
797             except KeyError:
798                 # No value may have been set if they tried to pass in a
799                 # reserved variable name like TARGETS.
800                 pass
801
802         if tools is None:
803             tools = self._dict.get('TOOLS', None)
804             if tools is None:
805                 tools = ['default']
806         apply_tools(self, tools, toolpath)
807
808         # Now restore the passed-in variables and customized options
809         # to the environment, since the values the user set explicitly
810         # should override any values set by the tools.
811         for key, val in save.items():
812             self._dict[key] = val
813
814     #######################################################################
815     # Utility methods that are primarily for internal use by SCons.
816     # These begin with lower-case letters.
817     #######################################################################
818
819     def get_builder(self, name):
820         """Fetch the builder with the specified name from the environment.
821         """
822         try:
823             return self._dict['BUILDERS'][name]
824         except KeyError:
825             return None
826
827     def get_calculator(self):
828         try:
829             module = self._calc_module
830             c = apply(SCons.Sig.Calculator, (module,), CalculatorArgs)
831         except AttributeError:
832             # Note that we're calling get_calculator() here, so the
833             # DefaultEnvironment() must have a _calc_module attribute
834             # to avoid infinite recursion.
835             c = SCons.Defaults.DefaultEnvironment().get_calculator()
836         return c
837
838     def get_factory(self, factory, default='File'):
839         """Return a factory function for creating Nodes for this
840         construction environment.
841         """
842         name = default
843         try:
844             is_node = issubclass(factory, SCons.Node.Node)
845         except TypeError:
846             # The specified factory isn't a Node itself--it's
847             # most likely None, or possibly a callable.
848             pass
849         else:
850             if is_node:
851                 # The specified factory is a Node (sub)class.  Try to
852                 # return the FS method that corresponds to the Node's
853                 # name--that is, we return self.fs.Dir if they want a Dir,
854                 # self.fs.File for a File, etc.
855                 try: name = factory.__name__
856                 except AttributeError: pass
857                 else: factory = None
858         if not factory:
859             # They passed us None, or we picked up a name from a specified
860             # class, so return the FS method.  (Note that we *don't*
861             # use our own self.{Dir,File} methods because that would
862             # cause env.subst() to be called twice on the file name,
863             # interfering with files that have $$ in them.)
864             factory = getattr(self.fs, name)
865         return factory
866
867     memoizer_counters.append(SCons.Memoize.CountValue('_gsm'))
868
869     def _gsm(self):
870         try:
871             return self._memo['_gsm']
872         except KeyError:
873             pass
874
875         result = {}
876
877         try:
878             scanners = self._dict['SCANNERS']
879         except KeyError:
880             pass
881         else:
882             # Reverse the scanner list so that, if multiple scanners
883             # claim they can scan the same suffix, earlier scanners
884             # in the list will overwrite later scanners, so that
885             # the result looks like a "first match" to the user.
886             if not SCons.Util.is_List(scanners):
887                 scanners = [scanners]
888             else:
889                 scanners = scanners[:] # copy so reverse() doesn't mod original
890             scanners.reverse()
891             for scanner in scanners:
892                 for k in scanner.get_skeys(self):
893                     result[k] = scanner
894
895         self._memo['_gsm'] = result
896
897         return result
898         
899     def get_scanner(self, skey):
900         """Find the appropriate scanner given a key (usually a file suffix).
901         """
902         return self._gsm().get(skey)
903
904     def scanner_map_delete(self, kw=None):
905         """Delete the cached scanner map (if we need to).
906         """
907         try:
908             del self._memo['_gsm']
909         except KeyError:
910             pass
911
912     def _update(self, dict):
913         """Update an environment's values directly, bypassing the normal
914         checks that occur when users try to set items.
915         """
916         self._dict.update(dict)
917
918     def use_build_signature(self):
919         try:
920             return self._build_signature
921         except AttributeError:
922             b = SCons.Defaults.DefaultEnvironment()._build_signature
923             self._build_signature = b
924             return b
925
926     #######################################################################
927     # Public methods for manipulating an Environment.  These begin with
928     # upper-case letters.  The essential characteristic of methods in
929     # this section is that they do *not* have corresponding same-named
930     # global functions.  For example, a stand-alone Append() function
931     # makes no sense, because Append() is all about appending values to
932     # an Environment's construction variables.
933     #######################################################################
934
935     def Append(self, **kw):
936         """Append values to existing construction variables
937         in an Environment.
938         """
939         kw = copy_non_reserved_keywords(kw)
940         for key, val in kw.items():
941             # It would be easier on the eyes to write this using
942             # "continue" statements whenever we finish processing an item,
943             # but Python 1.5.2 apparently doesn't let you use "continue"
944             # within try:-except: blocks, so we have to nest our code.
945             try:
946                 orig = self._dict[key]
947             except KeyError:
948                 # No existing variable in the environment, so just set
949                 # it to the new value.
950                 self._dict[key] = val
951             else:
952                 try:
953                     # Check if the original looks like a dictionary.
954                     # If it is, we can't just try adding the value because
955                     # dictionaries don't have __add__() methods, and
956                     # things like UserList will incorrectly coerce the
957                     # original dict to a list (which we don't want).
958                     update_dict = orig.update
959                 except AttributeError:
960                     try:
961                         # Most straightforward:  just try to add them
962                         # together.  This will work in most cases, when the
963                         # original and new values are of compatible types.
964                         self._dict[key] = orig + val
965                     except (KeyError, TypeError):
966                         try:
967                             # Check if the original is a list.
968                             add_to_orig = orig.append
969                         except AttributeError:
970                             # The original isn't a list, but the new
971                             # value is (by process of elimination),
972                             # so insert the original in the new value
973                             # (if there's one to insert) and replace
974                             # the variable with it.
975                             if orig:
976                                 val.insert(0, orig)
977                             self._dict[key] = val
978                         else:
979                             # The original is a list, so append the new
980                             # value to it (if there's a value to append).
981                             if val:
982                                 add_to_orig(val)
983                 else:
984                     # The original looks like a dictionary, so update it
985                     # based on what we think the value looks like.
986                     if SCons.Util.is_List(val):
987                         for v in val:
988                             orig[v] = None
989                     else:
990                         try:
991                             update_dict(val)
992                         except (AttributeError, TypeError, ValueError):
993                             orig[val] = None
994         self.scanner_map_delete(kw)
995
996     def AppendENVPath(self, name, newpath, envname = 'ENV', sep = os.pathsep):
997         """Append path elements to the path 'name' in the 'ENV'
998         dictionary for this environment.  Will only add any particular
999         path once, and will normpath and normcase all paths to help
1000         assure this.  This can also handle the case where the env
1001         variable is a list instead of a string.
1002         """
1003
1004         orig = ''
1005         if self._dict.has_key(envname) and self._dict[envname].has_key(name):
1006             orig = self._dict[envname][name]
1007
1008         nv = SCons.Util.AppendPath(orig, newpath, sep)
1009             
1010         if not self._dict.has_key(envname):
1011             self._dict[envname] = {}
1012
1013         self._dict[envname][name] = nv
1014
1015     def AppendUnique(self, **kw):
1016         """Append values to existing construction variables
1017         in an Environment, if they're not already there.
1018         """
1019         kw = copy_non_reserved_keywords(kw)
1020         for key, val in kw.items():
1021             if not self._dict.has_key(key) or self._dict[key] in ('', None):
1022                 self._dict[key] = val
1023             elif SCons.Util.is_Dict(self._dict[key]) and \
1024                  SCons.Util.is_Dict(val):
1025                 self._dict[key].update(val)
1026             elif SCons.Util.is_List(val):
1027                 dk = self._dict[key]
1028                 if not SCons.Util.is_List(dk):
1029                     dk = [dk]
1030                 val = filter(lambda x, dk=dk: x not in dk, val)
1031                 self._dict[key] = dk + val
1032             else:
1033                 dk = self._dict[key]
1034                 if SCons.Util.is_List(dk):
1035                     # By elimination, val is not a list.  Since dk is a
1036                     # list, wrap val in a list first.
1037                     if not val in dk:
1038                         self._dict[key] = dk + [val]
1039                 else:
1040                     self._dict[key] = self._dict[key] + val
1041         self.scanner_map_delete(kw)
1042
1043     def Clone(self, tools=[], toolpath=None, **kw):
1044         """Return a copy of a construction Environment.  The
1045         copy is like a Python "deep copy"--that is, independent
1046         copies are made recursively of each objects--except that
1047         a reference is copied when an object is not deep-copyable
1048         (like a function).  There are no references to any mutable
1049         objects in the original Environment.
1050         """
1051         clone = copy.copy(self)
1052         clone._dict = our_deepcopy(self._dict)
1053         try:
1054             cbd = clone._dict['BUILDERS']
1055             clone._dict['BUILDERS'] = BuilderDict(cbd, clone)
1056         except KeyError:
1057             pass
1058
1059         clone._memo = {}
1060
1061         apply_tools(clone, tools, toolpath)
1062
1063         # Apply passed-in variables after the new tools.
1064         kw = copy_non_reserved_keywords(kw)
1065         new = {}
1066         for key, value in kw.items():
1067             new[key] = SCons.Subst.scons_subst_once(value, self, key)
1068         apply(clone.Replace, (), new)
1069         if __debug__: logInstanceCreation(self, 'Environment.EnvironmentClone')
1070         return clone
1071
1072     def Copy(self, *args, **kw):
1073         return apply(self.Clone, args, kw)
1074
1075     def Detect(self, progs):
1076         """Return the first available program in progs.
1077         """
1078         if not SCons.Util.is_List(progs):
1079             progs = [ progs ]
1080         for prog in progs:
1081             path = self.WhereIs(prog)
1082             if path: return prog
1083         return None
1084
1085     def Dictionary(self, *args):
1086         if not args:
1087             return self._dict
1088         dlist = map(lambda x, s=self: s._dict[x], args)
1089         if len(dlist) == 1:
1090             dlist = dlist[0]
1091         return dlist
1092
1093     def Dump(self, key = None):
1094         """
1095         Using the standard Python pretty printer, dump the contents of the
1096         scons build environment to stdout.
1097
1098         If the key passed in is anything other than None, then that will
1099         be used as an index into the build environment dictionary and
1100         whatever is found there will be fed into the pretty printer. Note
1101         that this key is case sensitive.
1102         """
1103         import pprint
1104         pp = pprint.PrettyPrinter(indent=2)
1105         if key:
1106             dict = self.Dictionary(key)
1107         else:
1108             dict = self.Dictionary()
1109         return pp.pformat(dict)
1110
1111     def FindIxes(self, paths, prefix, suffix):
1112         """
1113         Search a list of paths for something that matches the prefix and suffix.
1114
1115         paths - the list of paths or nodes.
1116         prefix - construction variable for the prefix.
1117         suffix - construction variable for the suffix.
1118         """
1119
1120         suffix = self.subst('$'+suffix)
1121         prefix = self.subst('$'+prefix)
1122
1123         for path in paths:
1124             dir,name = os.path.split(str(path))
1125             if name[:len(prefix)] == prefix and name[-len(suffix):] == suffix: 
1126                 return path
1127
1128     def ParseConfig(self, command, function=None, unique=1):
1129         """
1130         Use the specified function to parse the output of the command
1131         in order to modify the current environment.  The 'command' can
1132         be a string or a list of strings representing a command and
1133         its arguments.  'Function' is an optional argument that takes
1134         the environment, the output of the command, and the unique flag.
1135         If no function is specified, MergeFlags, which treats the output
1136         as the result of a typical 'X-config' command (i.e. gtk-config),
1137         will merge the output into the appropriate variables.
1138         """
1139         if function is None:
1140             def parse_conf(env, cmd, unique=unique):
1141                 return env.MergeFlags(cmd, unique)
1142             function = parse_conf
1143         if SCons.Util.is_List(command):
1144             command = string.join(command)
1145         command = self.subst(command)
1146         return function(self, self.backtick(command))
1147
1148     def ParseDepends(self, filename, must_exist=None, only_one=0):
1149         """
1150         Parse a mkdep-style file for explicit dependencies.  This is
1151         completely abusable, and should be unnecessary in the "normal"
1152         case of proper SCons configuration, but it may help make
1153         the transition from a Make hierarchy easier for some people
1154         to swallow.  It can also be genuinely useful when using a tool
1155         that can write a .d file, but for which writing a scanner would
1156         be too complicated.
1157         """
1158         filename = self.subst(filename)
1159         try:
1160             fp = open(filename, 'r')
1161         except IOError:
1162             if must_exist:
1163                 raise
1164             return
1165         lines = SCons.Util.LogicalLines(fp).readlines()
1166         lines = filter(lambda l: l[0] != '#', lines)
1167         tdlist = []
1168         for line in lines:
1169             try:
1170                 target, depends = string.split(line, ':', 1)
1171             except (AttributeError, TypeError, ValueError):
1172                 # Python 1.5.2 throws TypeError if line isn't a string,
1173                 # Python 2.x throws AttributeError because it tries
1174                 # to call line.split().  Either can throw ValueError
1175                 # if the line doesn't split into two or more elements.
1176                 pass
1177             else:
1178                 tdlist.append((string.split(target), string.split(depends)))
1179         if only_one:
1180             targets = reduce(lambda x, y: x+y, map(lambda p: p[0], tdlist))
1181             if len(targets) > 1:
1182                 raise SCons.Errors.UserError, "More than one dependency target found in `%s':  %s" % (filename, targets)
1183         for target, depends in tdlist:
1184             self.Depends(target, depends)
1185
1186     def Platform(self, platform):
1187         platform = self.subst(platform)
1188         return SCons.Platform.Platform(platform)(self)
1189
1190     def Prepend(self, **kw):
1191         """Prepend values to existing construction variables
1192         in an Environment.
1193         """
1194         kw = copy_non_reserved_keywords(kw)
1195         for key, val in kw.items():
1196             # It would be easier on the eyes to write this using
1197             # "continue" statements whenever we finish processing an item,
1198             # but Python 1.5.2 apparently doesn't let you use "continue"
1199             # within try:-except: blocks, so we have to nest our code.
1200             try:
1201                 orig = self._dict[key]
1202             except KeyError:
1203                 # No existing variable in the environment, so just set
1204                 # it to the new value.
1205                 self._dict[key] = val
1206             else:
1207                 try:
1208                     # Check if the original looks like a dictionary.
1209                     # If it is, we can't just try adding the value because
1210                     # dictionaries don't have __add__() methods, and
1211                     # things like UserList will incorrectly coerce the
1212                     # original dict to a list (which we don't want).
1213                     update_dict = orig.update
1214                 except AttributeError:
1215                     try:
1216                         # Most straightforward:  just try to add them
1217                         # together.  This will work in most cases, when the
1218                         # original and new values are of compatible types.
1219                         self._dict[key] = val + orig
1220                     except (KeyError, TypeError):
1221                         try:
1222                             # Check if the added value is a list.
1223                             add_to_val = val.append
1224                         except AttributeError:
1225                             # The added value isn't a list, but the
1226                             # original is (by process of elimination),
1227                             # so insert the the new value in the original
1228                             # (if there's one to insert).
1229                             if val:
1230                                 orig.insert(0, val)
1231                         else:
1232                             # The added value is a list, so append
1233                             # the original to it (if there's a value
1234                             # to append).
1235                             if orig:
1236                                 add_to_val(orig)
1237                             self._dict[key] = val
1238                 else:
1239                     # The original looks like a dictionary, so update it
1240                     # based on what we think the value looks like.
1241                     if SCons.Util.is_List(val):
1242                         for v in val:
1243                             orig[v] = None
1244                     else:
1245                         try:
1246                             update_dict(val)
1247                         except (AttributeError, TypeError, ValueError):
1248                             orig[val] = None
1249         self.scanner_map_delete(kw)
1250
1251     def PrependENVPath(self, name, newpath, envname = 'ENV', sep = os.pathsep):
1252         """Prepend path elements to the path 'name' in the 'ENV'
1253         dictionary for this environment.  Will only add any particular
1254         path once, and will normpath and normcase all paths to help
1255         assure this.  This can also handle the case where the env
1256         variable is a list instead of a string.
1257         """
1258
1259         orig = ''
1260         if self._dict.has_key(envname) and self._dict[envname].has_key(name):
1261             orig = self._dict[envname][name]
1262
1263         nv = SCons.Util.PrependPath(orig, newpath, sep)
1264             
1265         if not self._dict.has_key(envname):
1266             self._dict[envname] = {}
1267
1268         self._dict[envname][name] = nv
1269
1270     def PrependUnique(self, **kw):
1271         """Append values to existing construction variables
1272         in an Environment, if they're not already there.
1273         """
1274         kw = copy_non_reserved_keywords(kw)
1275         for key, val in kw.items():
1276             if not self._dict.has_key(key) or self._dict[key] in ('', None):
1277                 self._dict[key] = val
1278             elif SCons.Util.is_Dict(self._dict[key]) and \
1279                  SCons.Util.is_Dict(val):
1280                 self._dict[key].update(val)
1281             elif SCons.Util.is_List(val):
1282                 dk = self._dict[key]
1283                 if not SCons.Util.is_List(dk):
1284                     dk = [dk]
1285                 val = filter(lambda x, dk=dk: x not in dk, val)
1286                 self._dict[key] = val + dk
1287             else:
1288                 dk = self._dict[key]
1289                 if SCons.Util.is_List(dk):
1290                     # By elimination, val is not a list.  Since dk is a
1291                     # list, wrap val in a list first.
1292                     if not val in dk:
1293                         self._dict[key] = [val] + dk
1294                 else:
1295                     self._dict[key] = val + dk
1296         self.scanner_map_delete(kw)
1297
1298     def Replace(self, **kw):
1299         """Replace existing construction variables in an Environment
1300         with new construction variables and/or values.
1301         """
1302         try:
1303             kwbd = our_deepcopy(kw['BUILDERS'])
1304             del kw['BUILDERS']
1305             self.__setitem__('BUILDERS', kwbd)
1306         except KeyError:
1307             pass
1308         kw = copy_non_reserved_keywords(kw)
1309         self._update(our_deepcopy(kw))
1310         self.scanner_map_delete(kw)
1311
1312     def ReplaceIxes(self, path, old_prefix, old_suffix, new_prefix, new_suffix):
1313         """
1314         Replace old_prefix with new_prefix and old_suffix with new_suffix.
1315
1316         env - Environment used to interpolate variables.
1317         path - the path that will be modified.
1318         old_prefix - construction variable for the old prefix.
1319         old_suffix - construction variable for the old suffix.
1320         new_prefix - construction variable for the new prefix.
1321         new_suffix - construction variable for the new suffix.
1322         """
1323         old_prefix = self.subst('$'+old_prefix)
1324         old_suffix = self.subst('$'+old_suffix)
1325
1326         new_prefix = self.subst('$'+new_prefix)
1327         new_suffix = self.subst('$'+new_suffix)
1328
1329         dir,name = os.path.split(str(path))
1330         if name[:len(old_prefix)] == old_prefix:
1331             name = name[len(old_prefix):]
1332         if name[-len(old_suffix):] == old_suffix:
1333             name = name[:-len(old_suffix)]
1334         return os.path.join(dir, new_prefix+name+new_suffix)
1335
1336     def SetDefault(self, **kw):
1337         for k in kw.keys():
1338             if self._dict.has_key(k):
1339                 del kw[k]
1340         apply(self.Replace, (), kw)
1341
1342     def _find_toolpath_dir(self, tp):
1343         return self.fs.Dir(self.subst(tp)).srcnode().abspath
1344
1345     def Tool(self, tool, toolpath=None, **kw):
1346         if SCons.Util.is_String(tool):
1347             tool = self.subst(tool)
1348             if toolpath is None:
1349                 toolpath = self.get('toolpath', [])
1350             toolpath = map(self._find_toolpath_dir, toolpath)
1351             tool = apply(SCons.Tool.Tool, (tool, toolpath), kw)
1352         tool(self)
1353
1354     def WhereIs(self, prog, path=None, pathext=None, reject=[]):
1355         """Find prog in the path.
1356         """
1357         if path is None:
1358             try:
1359                 path = self['ENV']['PATH']
1360             except KeyError:
1361                 pass
1362         elif SCons.Util.is_String(path):
1363             path = self.subst(path)
1364         if pathext is None:
1365             try:
1366                 pathext = self['ENV']['PATHEXT']
1367             except KeyError:
1368                 pass
1369         elif SCons.Util.is_String(pathext):
1370             pathext = self.subst(pathext)
1371         path = SCons.Util.WhereIs(prog, path, pathext, reject)
1372         if path: return path
1373         return None
1374
1375     #######################################################################
1376     # Public methods for doing real "SCons stuff" (manipulating
1377     # dependencies, setting attributes on targets, etc.).  These begin
1378     # with upper-case letters.  The essential characteristic of methods
1379     # in this section is that they all *should* have corresponding
1380     # same-named global functions.
1381     #######################################################################
1382
1383     def Action(self, *args, **kw):
1384         def subst_string(a, self=self):
1385             if SCons.Util.is_String(a):
1386                 a = self.subst(a)
1387             return a
1388         nargs = map(subst_string, args)
1389         nkw = self.subst_kw(kw)
1390         return apply(SCons.Action.Action, nargs, nkw)
1391
1392     def AddPreAction(self, files, action):
1393         nodes = self.arg2nodes(files, self.fs.Entry)
1394         action = SCons.Action.Action(action)
1395         uniq = {}
1396         for executor in map(lambda n: n.get_executor(), nodes):
1397             uniq[executor] = 1
1398         for executor in uniq.keys():
1399             executor.add_pre_action(action)
1400         return nodes
1401
1402     def AddPostAction(self, files, action):
1403         nodes = self.arg2nodes(files, self.fs.Entry)
1404         action = SCons.Action.Action(action)
1405         uniq = {}
1406         for executor in map(lambda n: n.get_executor(), nodes):
1407             uniq[executor] = 1
1408         for executor in uniq.keys():
1409             executor.add_post_action(action)
1410         return nodes
1411
1412     def Alias(self, target, source=[], action=None, **kw):
1413         tlist = self.arg2nodes(target, self.ans.Alias)
1414         if not SCons.Util.is_List(source):
1415             source = [source]
1416         source = filter(None, source)
1417
1418         if not action:
1419             if not source:
1420                 # There are no source files and no action, so just
1421                 # return a target list of classic Alias Nodes, without
1422                 # any builder.  The externally visible effect is that
1423                 # this will make the wrapping Script.BuildTask class
1424                 # say that there's "Nothing to be done" for this Alias,
1425                 # instead of that it's "up to date."
1426                 return tlist
1427
1428             # No action, but there are sources.  Re-call all the target
1429             # builders to add the sources to each target.
1430             result = []
1431             for t in tlist:
1432                 bld = t.get_builder(AliasBuilder)
1433                 result.extend(bld(self, t, source))
1434             return result
1435
1436         nkw = self.subst_kw(kw)
1437         nkw.update({
1438             'action'            : SCons.Action.Action(action),
1439             'source_factory'    : self.fs.Entry,
1440             'multi'             : 1,
1441             'is_explicit'       : None,
1442         })
1443         bld = apply(SCons.Builder.Builder, (), nkw)
1444
1445         # Apply the Builder separately to each target so that the Aliases
1446         # stay separate.  If we did one "normal" Builder call with the
1447         # whole target list, then all of the target Aliases would be
1448         # associated under a single Executor.
1449         result = []
1450         for t in tlist:
1451             # Calling the convert() method will cause a new Executor to be
1452             # created from scratch, so we have to explicitly initialize
1453             # it with the target's existing sources, plus our new ones,
1454             # so nothing gets lost.
1455             b = t.get_builder()
1456             if b is None or b is AliasBuilder:
1457                 b = bld
1458             else:
1459                 nkw['action'] = b.action + action
1460                 b = apply(SCons.Builder.Builder, (), nkw)
1461             t.convert()
1462             result.extend(b(self, t, t.sources + source))
1463         return result
1464
1465     def AlwaysBuild(self, *targets):
1466         tlist = []
1467         for t in targets:
1468             tlist.extend(self.arg2nodes(t, self.fs.Entry))
1469         for t in tlist:
1470             t.set_always_build()
1471         return tlist
1472
1473     def BuildDir(self, build_dir, src_dir, duplicate=1):
1474         build_dir = self.arg2nodes(build_dir, self.fs.Dir)[0]
1475         src_dir = self.arg2nodes(src_dir, self.fs.Dir)[0]
1476         self.fs.BuildDir(build_dir, src_dir, duplicate)
1477
1478     def Builder(self, **kw):
1479         nkw = self.subst_kw(kw)
1480         return apply(SCons.Builder.Builder, [], nkw)
1481
1482     def CacheDir(self, path):
1483         self.fs.CacheDir(self.subst(path))
1484
1485     def Clean(self, targets, files):
1486         global CleanTargets
1487         tlist = self.arg2nodes(targets, self.fs.Entry)
1488         flist = self.arg2nodes(files, self.fs.Entry)
1489         for t in tlist:
1490             try:
1491                 CleanTargets[t].extend(flist)
1492             except KeyError:
1493                 CleanTargets[t] = flist
1494
1495     def Configure(self, *args, **kw):
1496         nargs = [self]
1497         if args:
1498             nargs = nargs + self.subst_list(args)[0]
1499         nkw = self.subst_kw(kw)
1500         nkw['_depth'] = kw.get('_depth', 0) + 1
1501         try:
1502             nkw['custom_tests'] = self.subst_kw(nkw['custom_tests'])
1503         except KeyError:
1504             pass
1505         return apply(SCons.SConf.SConf, nargs, nkw)
1506
1507     def Command(self, target, source, action, **kw):
1508         """Builds the supplied target files from the supplied
1509         source files using the supplied action.  Action may
1510         be any type that the Builder constructor will accept
1511         for an action."""
1512         bkw = {
1513             'action' : action,
1514             'target_factory' : self.fs.Entry,
1515             'source_factory' : self.fs.Entry,
1516         }
1517         try: bkw['source_scanner'] = kw['source_scanner']
1518         except KeyError: pass
1519         else: del kw['source_scanner']
1520         bld = apply(SCons.Builder.Builder, (), bkw)
1521         return apply(bld, (self, target, source), kw)
1522
1523     def Depends(self, target, dependency):
1524         """Explicity specify that 'target's depend on 'dependency'."""
1525         tlist = self.arg2nodes(target, self.fs.Entry)
1526         dlist = self.arg2nodes(dependency, self.fs.Entry)
1527         for t in tlist:
1528             t.add_dependency(dlist)
1529         return tlist
1530
1531     def Dir(self, name, *args, **kw):
1532         """
1533         """
1534         return apply(self.fs.Dir, (self.subst(name),) + args, kw)
1535
1536     def NoClean(self, *targets):
1537         """Tags a target so that it will not be cleaned by -c"""
1538         tlist = []
1539         for t in targets:
1540             tlist.extend(self.arg2nodes(t, self.fs.Entry))
1541         for t in tlist:
1542             t.set_noclean()
1543         return tlist
1544
1545     def NoCache(self, *targets):
1546         """Tags a target so that it will not be cached"""
1547         tlist = []
1548         for t in targets:
1549             tlist.extend(self.arg2nodes(t, self.fs.Entry))
1550         for t in tlist:
1551             t.set_nocache()
1552         return tlist
1553
1554     def Entry(self, name, *args, **kw):
1555         """
1556         """
1557         return apply(self.fs.Entry, (self.subst(name),) + args, kw)
1558
1559     def Environment(self, **kw):
1560         return apply(SCons.Environment.Environment, [], self.subst_kw(kw))
1561
1562     def Execute(self, action, *args, **kw):
1563         """Directly execute an action through an Environment
1564         """
1565         action = apply(self.Action, (action,) + args, kw)
1566         return action([], [], self)
1567
1568     def File(self, name, *args, **kw):
1569         """
1570         """
1571         return apply(self.fs.File, (self.subst(name),) + args, kw)
1572
1573     def FindFile(self, file, dirs):
1574         file = self.subst(file)
1575         nodes = self.arg2nodes(dirs, self.fs.Dir)
1576         return SCons.Node.FS.find_file(file, tuple(nodes))
1577
1578     def Flatten(self, sequence):
1579         return SCons.Util.flatten(sequence)
1580
1581     def GetBuildPath(self, files):
1582         result = map(str, self.arg2nodes(files, self.fs.Entry))
1583         if SCons.Util.is_List(files):
1584             return result
1585         else:
1586             return result[0]
1587
1588     def Ignore(self, target, dependency):
1589         """Ignore a dependency."""
1590         tlist = self.arg2nodes(target, self.fs.Entry)
1591         dlist = self.arg2nodes(dependency, self.fs.Entry)
1592         for t in tlist:
1593             t.add_ignore(dlist)
1594         return tlist
1595
1596     def Install(self, dir, source):
1597         """Install specified files in the given directory."""
1598         try:
1599             dnodes = self.arg2nodes(dir, self.fs.Dir)
1600         except TypeError:
1601             fmt = "Target `%s' of Install() is a file, but should be a directory.  Perhaps you have the Install() arguments backwards?"
1602             raise SCons.Errors.UserError, fmt % str(dir)
1603         try:
1604             sources = self.arg2nodes(source, self.fs.Entry)
1605         except TypeError:
1606             if SCons.Util.is_List(source):
1607                 s = repr(map(str, source))
1608             else:
1609                 s = str(source)
1610             fmt = "Source `%s' of Install() is neither a file nor a directory.  Install() source must be one or more files or directories"
1611             raise SCons.Errors.UserError, fmt % s
1612         tgt = []
1613         for dnode in dnodes:
1614             for src in sources:
1615                 # Prepend './' so the lookup doesn't interpret an initial
1616                 # '#' on the file name portion as meaning the Node should
1617                 # be relative to the top-level SConstruct directory.
1618                 target = dnode.Entry('.'+os.sep+src.name)
1619                 tgt.extend(InstallBuilder(self, target, src))
1620         return tgt
1621
1622     def InstallAs(self, target, source):
1623         """Install sources as targets."""
1624         sources = self.arg2nodes(source, self.fs.Entry)
1625         targets = self.arg2nodes(target, self.fs.Entry)
1626         if len(sources) != len(targets):
1627             if not SCons.Util.is_List(target):
1628                 target = [target]
1629             if not SCons.Util.is_List(source):
1630                 source = [source]
1631             t = repr(map(str, target))
1632             s = repr(map(str, source))
1633             fmt = "Target (%s) and source (%s) lists of InstallAs() must be the same length."
1634             raise SCons.Errors.UserError, fmt % (t, s)
1635         result = []
1636         for src, tgt in map(lambda x, y: (x, y), sources, targets):
1637             result.extend(InstallBuilder(self, tgt, src))
1638         return result
1639
1640     def Literal(self, string):
1641         return SCons.Subst.Literal(string)
1642
1643     def Local(self, *targets):
1644         ret = []
1645         for targ in targets:
1646             if isinstance(targ, SCons.Node.Node):
1647                 targ.set_local()
1648                 ret.append(targ)
1649             else:
1650                 for t in self.arg2nodes(targ, self.fs.Entry):
1651                    t.set_local()
1652                    ret.append(t)
1653         return ret
1654
1655     def Precious(self, *targets):
1656         tlist = []
1657         for t in targets:
1658             tlist.extend(self.arg2nodes(t, self.fs.Entry))
1659         for t in tlist:
1660             t.set_precious()
1661         return tlist
1662
1663     def Repository(self, *dirs, **kw):
1664         dirs = self.arg2nodes(list(dirs), self.fs.Dir)
1665         apply(self.fs.Repository, dirs, kw)
1666
1667     def Scanner(self, *args, **kw):
1668         nargs = []
1669         for arg in args:
1670             if SCons.Util.is_String(arg):
1671                 arg = self.subst(arg)
1672             nargs.append(arg)
1673         nkw = self.subst_kw(kw)
1674         return apply(SCons.Scanner.Base, nargs, nkw)
1675
1676     def SConsignFile(self, name=".sconsign", dbm_module=None):
1677         if not name is None:
1678             name = self.subst(name)
1679             if not os.path.isabs(name):
1680                 name = os.path.join(str(self.fs.SConstruct_dir), name)
1681         SCons.SConsign.File(name, dbm_module)
1682
1683     def SideEffect(self, side_effect, target):
1684         """Tell scons that side_effects are built as side 
1685         effects of building targets."""
1686         side_effects = self.arg2nodes(side_effect, self.fs.Entry)
1687         targets = self.arg2nodes(target, self.fs.Entry)
1688
1689         for side_effect in side_effects:
1690             if side_effect.multiple_side_effect_has_builder():
1691                 raise SCons.Errors.UserError, "Multiple ways to build the same target were specified for: %s" % str(side_effect)
1692             side_effect.add_source(targets)
1693             side_effect.side_effect = 1
1694             self.Precious(side_effect)
1695             for target in targets:
1696                 target.side_effects.append(side_effect)
1697         return side_effects
1698
1699     def SourceCode(self, entry, builder):
1700         """Arrange for a source code builder for (part of) a tree."""
1701         entries = self.arg2nodes(entry, self.fs.Entry)
1702         for entry in entries:
1703             entry.set_src_builder(builder)
1704         return entries
1705
1706     def SourceSignatures(self, type):
1707         type = self.subst(type)
1708         if type == 'MD5':
1709             try:
1710                 import SCons.Sig.MD5
1711             except ImportError:
1712                 msg = "No MD5 module available, using time stamps"
1713                 SCons.Warnings.warn(SCons.Warnings.NoMD5ModuleWarning, msg)
1714                 import SCons.Sig.TimeStamp
1715                 self._calc_module = SCons.Sig.TimeStamp
1716             else:
1717                 self._calc_module = SCons.Sig.MD5
1718         elif type == 'timestamp':
1719             import SCons.Sig.TimeStamp
1720             self._calc_module = SCons.Sig.TimeStamp
1721         else:
1722             raise UserError, "Unknown source signature type '%s'"%type
1723
1724     def Split(self, arg):
1725         """This function converts a string or list into a list of strings
1726         or Nodes.  This makes things easier for users by allowing files to
1727         be specified as a white-space separated list to be split.
1728         The input rules are:
1729             - A single string containing names separated by spaces. These will be
1730               split apart at the spaces.
1731             - A single Node instance
1732             - A list containing either strings or Node instances. Any strings
1733               in the list are not split at spaces.
1734         In all cases, the function returns a list of Nodes and strings."""
1735         if SCons.Util.is_List(arg):
1736             return map(self.subst, arg)
1737         elif SCons.Util.is_String(arg):
1738             return string.split(self.subst(arg))
1739         else:
1740             return [self.subst(arg)]
1741
1742     def TargetSignatures(self, type):
1743         type = self.subst(type)
1744         if type == 'build':
1745             self._build_signature = 1
1746         elif type == 'content':
1747             self._build_signature = 0
1748         else:
1749             raise SCons.Errors.UserError, "Unknown target signature type '%s'"%type
1750
1751     def Value(self, value, built_value=None):
1752         """
1753         """
1754         return SCons.Node.Python.Value(value, built_value)
1755
1756 class OverrideEnvironment(Base):
1757     """A proxy that overrides variables in a wrapped construction
1758     environment by returning values from an overrides dictionary in
1759     preference to values from the underlying subject environment.
1760
1761     This is a lightweight (I hope) proxy that passes through most use of
1762     attributes to the underlying Environment.Base class, but has just
1763     enough additional methods defined to act like a real construction
1764     environment with overridden values.  It can wrap either a Base
1765     construction environment, or another OverrideEnvironment, which
1766     can in turn nest arbitrary OverrideEnvironments...
1767
1768     Note that we do *not* call the underlying base class
1769     (SubsitutionEnvironment) initialization, because we get most of those
1770     from proxying the attributes of the subject construction environment.
1771     But because we subclass SubstitutionEnvironment, this class also
1772     has inherited arg2nodes() and subst*() methods; those methods can't
1773     be proxied because they need *this* object's methods to fetch the
1774     values from the overrides dictionary.
1775     """
1776
1777     if SCons.Memoize.use_memoizer:
1778         __metaclass__ = SCons.Memoize.Memoized_Metaclass
1779
1780     def __init__(self, subject, overrides={}):
1781         if __debug__: logInstanceCreation(self, 'Environment.OverrideEnvironment')
1782         self.__dict__['__subject'] = subject
1783         self.__dict__['overrides'] = overrides
1784
1785     # Methods that make this class act like a proxy.
1786     def __getattr__(self, name):
1787         return getattr(self.__dict__['__subject'], name)
1788     def __setattr__(self, name, value):
1789         setattr(self.__dict__['__subject'], name, value)
1790
1791     # Methods that make this class act like a dictionary.
1792     def __getitem__(self, key):
1793         try:
1794             return self.__dict__['overrides'][key]
1795         except KeyError:
1796             return self.__dict__['__subject'].__getitem__(key)
1797     def __setitem__(self, key, value):
1798         if not SCons.Util.is_valid_construction_var(key):
1799             raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
1800         self.__dict__['overrides'][key] = value
1801     def __delitem__(self, key):
1802         try:
1803             del self.__dict__['overrides'][key]
1804         except KeyError:
1805             deleted = 0
1806         else:
1807             deleted = 1
1808         try:
1809             result = self.__dict__['__subject'].__delitem__(key)
1810         except KeyError:
1811             if not deleted:
1812                 raise
1813             result = None
1814         return result
1815     def get(self, key, default=None):
1816         """Emulates the get() method of dictionaries."""
1817         try:
1818             return self.__dict__['overrides'][key]
1819         except KeyError:
1820             return self.__dict__['__subject'].get(key, default)
1821     def has_key(self, key):
1822         try:
1823             self.__dict__['overrides'][key]
1824             return 1
1825         except KeyError:
1826             return self.__dict__['__subject'].has_key(key)
1827     def Dictionary(self):
1828         """Emulates the items() method of dictionaries."""
1829         d = self.__dict__['__subject'].Dictionary().copy()
1830         d.update(self.__dict__['overrides'])
1831         return d
1832     def items(self):
1833         """Emulates the items() method of dictionaries."""
1834         return self.Dictionary().items()
1835
1836     # Overridden private construction environment methods.
1837     def _update(self, dict):
1838         """Update an environment's values directly, bypassing the normal
1839         checks that occur when users try to set items.
1840         """
1841         self.__dict__['overrides'].update(dict)
1842
1843     def gvars(self):
1844         return self.__dict__['__subject'].gvars()
1845
1846     def lvars(self):
1847         lvars = self.__dict__['__subject'].lvars()
1848         lvars.update(self.__dict__['overrides'])
1849         return lvars
1850
1851     # Overridden public construction environment methods.
1852     def Replace(self, **kw):
1853         kw = copy_non_reserved_keywords(kw)
1854         self.__dict__['overrides'].update(our_deepcopy(kw))
1855
1856 # The entry point that will be used by the external world
1857 # to refer to a construction environment.  This allows the wrapper
1858 # interface to extend a construction environment for its own purposes
1859 # by subclassing SCons.Environment.Base and then assigning the
1860 # class to SCons.Environment.Environment.
1861
1862 Environment = Base
1863
1864 # An entry point for returning a proxy subclass instance that overrides
1865 # the subst*() methods so they don't actually perform construction
1866 # variable substitution.  This is specifically intended to be the shim
1867 # layer in between global function calls (which don't want construction
1868 # variable substitution) and the DefaultEnvironment() (which would
1869 # substitute variables if left to its own devices)."""
1870 #
1871 # We have to wrap this in a function that allows us to delay definition of
1872 # the class until it's necessary, so that when it subclasses Environment
1873 # it will pick up whatever Environment subclass the wrapper interface
1874 # might have assigned to SCons.Environment.Environment.
1875
1876 def NoSubstitutionProxy(subject):
1877     class _NoSubstitutionProxy(Environment):
1878         def __init__(self, subject):
1879             self.__dict__['__subject'] = subject
1880         def __getattr__(self, name):
1881             return getattr(self.__dict__['__subject'], name)
1882         def __setattr__(self, name, value):
1883             return setattr(self.__dict__['__subject'], name, value)
1884         def raw_to_mode(self, dict):
1885             try:
1886                 raw = dict['raw']
1887             except KeyError:
1888                 pass
1889             else:
1890                 del dict['raw']
1891                 dict['mode'] = raw
1892         def subst(self, string, *args, **kwargs):
1893             return string
1894         def subst_kw(self, kw, *args, **kwargs):
1895             return kw
1896         def subst_list(self, string, *args, **kwargs):
1897             nargs = (string, self,) + args
1898             nkw = kwargs.copy()
1899             nkw['gvars'] = {}
1900             self.raw_to_mode(nkw)
1901             return apply(SCons.Subst.scons_subst_list, nargs, nkw)
1902         def subst_target_source(self, string, *args, **kwargs):
1903             nargs = (string, self,) + args
1904             nkw = kwargs.copy()
1905             nkw['gvars'] = {}
1906             self.raw_to_mode(nkw)
1907             return apply(SCons.Subst.scons_subst, nargs, nkw)
1908     return _NoSubstitutionProxy(subject)