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