http://scons.tigris.org/issues/show_bug.cgi?id=2345
[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 from __future__ import generators  ### KEEP FOR COMPATIBILITY FIXERS
35
36 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
37
38
39 import copy
40 import os
41 import sys
42 import re
43 import shlex
44 from collections import UserDict
45
46 import SCons.Action
47 import SCons.Builder
48 from SCons.Debug import logInstanceCreation
49 import SCons.Defaults
50 import SCons.Errors
51 import SCons.Memoize
52 import SCons.Node
53 import SCons.Node.Alias
54 import SCons.Node.FS
55 import SCons.Node.Python
56 import SCons.Platform
57 import SCons.SConf
58 import SCons.SConsign
59 import SCons.Subst
60 import SCons.Tool
61 import SCons.Util
62 import SCons.Warnings
63
64 class _Null:
65     pass
66
67 _null = _Null
68
69 _warn_copy_deprecated = True
70 _warn_source_signatures_deprecated = True
71 _warn_target_signatures_deprecated = True
72
73 CleanTargets = {}
74 CalculatorArgs = {}
75
76 semi_deepcopy = SCons.Util.semi_deepcopy
77
78 # Pull UserError into the global name space for the benefit of
79 # Environment().SourceSignatures(), which has some import statements
80 # which seem to mess up its ability to reference SCons directly.
81 UserError = SCons.Errors.UserError
82
83 def alias_builder(env, target, source):
84     pass
85
86 AliasBuilder = SCons.Builder.Builder(action = alias_builder,
87                                      target_factory = SCons.Node.Alias.default_ans.Alias,
88                                      source_factory = SCons.Node.FS.Entry,
89                                      multi = 1,
90                                      is_explicit = None,
91                                      name='AliasBuilder')
92
93 def apply_tools(env, tools, toolpath):
94     # Store the toolpath in the Environment.
95     if toolpath is not None:
96         env['toolpath'] = toolpath
97
98     if not tools:
99         return
100     # Filter out null tools from the list.
101     for tool in [_f for _f in tools if _f]:
102         if SCons.Util.is_List(tool) or isinstance(tool, tuple):
103             toolname = tool[0]
104             toolargs = tool[1] # should be a dict of kw args
105             tool = env.Tool(toolname, **toolargs)
106         else:
107             env.Tool(tool)
108
109 # These names are (or will be) controlled by SCons; users should never
110 # set or override them.  This warning can optionally be turned off,
111 # but scons will still ignore the illegal variable names even if it's off.
112 reserved_construction_var_names = [
113     'CHANGED_SOURCES',
114     'CHANGED_TARGETS',
115     'SOURCE',
116     'SOURCES',
117     'TARGET',
118     'TARGETS',
119     'UNCHANGED_SOURCES',
120     'UNCHANGED_TARGETS',
121 ]
122
123 future_reserved_construction_var_names = [
124     #'HOST_OS',
125     #'HOST_ARCH',
126     #'HOST_CPU',
127     ]
128
129 def copy_non_reserved_keywords(dict):
130     result = semi_deepcopy(dict)
131     for k in result.keys():
132         if k in reserved_construction_var_names:
133             msg = "Ignoring attempt to set reserved variable `$%s'"
134             SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning, msg % k)
135             del result[k]
136     return result
137
138 def _set_reserved(env, key, value):
139     msg = "Ignoring attempt to set reserved variable `$%s'"
140     SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning, msg % key)
141
142 def _set_future_reserved(env, key, value):
143     env._dict[key] = value
144     msg = "`$%s' will be reserved in a future release and setting it will become ignored"
145     SCons.Warnings.warn(SCons.Warnings.FutureReservedVariableWarning, msg % key)
146
147 def _set_BUILDERS(env, key, value):
148     try:
149         bd = env._dict[key]
150         for k in bd.keys():
151             del bd[k]
152     except KeyError:
153         bd = BuilderDict(kwbd, env)
154         env._dict[key] = bd
155     for k, v in value.items():
156         if not SCons.Builder.is_a_Builder(v):
157             raise SCons.Errors.UserError('%s is not a Builder.' % repr(v))
158     bd.update(value)
159
160 def _del_SCANNERS(env, key):
161     del env._dict[key]
162     env.scanner_map_delete()
163
164 def _set_SCANNERS(env, key, value):
165     env._dict[key] = value
166     env.scanner_map_delete()
167
168 def _delete_duplicates(l, keep_last):
169     """Delete duplicates from a sequence, keeping the first or last."""
170     seen={}
171     result=[]
172     if keep_last:           # reverse in & out, then keep first
173         l.reverse()
174     for i in l:
175         try:
176             if i not in seen:
177                 result.append(i)
178                 seen[i]=1
179         except TypeError:
180             # probably unhashable.  Just keep it.
181             result.append(i)
182     if keep_last:
183         result.reverse()
184     return result
185
186
187
188 # The following is partly based on code in a comment added by Peter
189 # Shannon at the following page (there called the "transplant" class):
190 #
191 # ASPN : Python Cookbook : Dynamically added methods to a class
192 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81732
193 #
194 # We had independently been using the idiom as BuilderWrapper, but
195 # factoring out the common parts into this base class, and making
196 # BuilderWrapper a subclass that overrides __call__() to enforce specific
197 # Builder calling conventions, simplified some of our higher-layer code.
198
199 class MethodWrapper:
200     """
201     A generic Wrapper class that associates a method (which can
202     actually be any callable) with an object.  As part of creating this
203     MethodWrapper object an attribute with the specified (by default,
204     the name of the supplied method) is added to the underlying object.
205     When that new "method" is called, our __call__() method adds the
206     object as the first argument, simulating the Python behavior of
207     supplying "self" on method calls.
208
209     We hang on to the name by which the method was added to the underlying
210     base class so that we can provide a method to "clone" ourselves onto
211     a new underlying object being copied (without which we wouldn't need
212     to save that info).
213     """
214     def __init__(self, object, method, name=None):
215         if name is None:
216             name = method.__name__
217         self.object = object
218         self.method = method
219         self.name = name
220         setattr(self.object, name, self)
221
222     def __call__(self, *args, **kwargs):
223         nargs = (self.object,) + args
224         return self.method(*nargs, **kwargs)
225
226     def clone(self, new_object):
227         """
228         Returns an object that re-binds the underlying "method" to
229         the specified new object.
230         """
231         return self.__class__(new_object, self.method, self.name)
232
233 class BuilderWrapper(MethodWrapper):
234     """
235     A MethodWrapper subclass that that associates an environment with
236     a Builder.
237
238     This mainly exists to wrap the __call__() function so that all calls
239     to Builders can have their argument lists massaged in the same way
240     (treat a lone argument as the source, treat two arguments as target
241     then source, make sure both target and source are lists) without
242     having to have cut-and-paste code to do it.
243
244     As a bit of obsessive backwards compatibility, we also intercept
245     attempts to get or set the "env" or "builder" attributes, which were
246     the names we used before we put the common functionality into the
247     MethodWrapper base class.  We'll keep this around for a while in case
248     people shipped Tool modules that reached into the wrapper (like the
249     Tool/qt.py module does, or did).  There shouldn't be a lot attribute
250     fetching or setting on these, so a little extra work shouldn't hurt.
251     """
252     def __call__(self, target=None, source=_null, *args, **kw):
253         if source is _null:
254             source = target
255             target = None
256         if target is not None and not SCons.Util.is_List(target):
257             target = [target]
258         if source is not None and not SCons.Util.is_List(source):
259             source = [source]
260         return MethodWrapper.__call__(self, target, source, *args, **kw)
261
262     def __repr__(self):
263         return '<BuilderWrapper %s>' % repr(self.name)
264
265     def __str__(self):
266         return self.__repr__()
267
268     def __getattr__(self, name):
269         if name == 'env':
270             return self.object
271         elif name == 'builder':
272             return self.method
273         else:
274             raise AttributeError(name)
275
276     def __setattr__(self, name, value):
277         if name == 'env':
278             self.object = value
279         elif name == 'builder':
280             self.method = value
281         else:
282             self.__dict__[name] = value
283
284     # This allows a Builder to be executed directly
285     # through the Environment to which it's attached.
286     # In practice, we shouldn't need this, because
287     # builders actually get executed through a Node.
288     # But we do have a unit test for this, and can't
289     # yet rule out that it would be useful in the
290     # future, so leave it for now.
291     #def execute(self, **kw):
292     #    kw['env'] = self.env
293     #    self.builder.execute(**kw)
294
295 class BuilderDict(UserDict):
296     """This is a dictionary-like class used by an Environment to hold
297     the Builders.  We need to do this because every time someone changes
298     the Builders in the Environment's BUILDERS dictionary, we must
299     update the Environment's attributes."""
300     def __init__(self, dict, env):
301         # Set self.env before calling the superclass initialization,
302         # because it will end up calling our other methods, which will
303         # need to point the values in this dictionary to self.env.
304         self.env = env
305         UserDict.__init__(self, dict)
306
307     def __semi_deepcopy__(self):
308         return self.__class__(self.data, self.env)
309
310     def __setitem__(self, item, val):
311         try:
312             method = getattr(self.env, item).method
313         except AttributeError:
314             pass
315         else:
316             self.env.RemoveMethod(method)
317         UserDict.__setitem__(self, item, val)
318         BuilderWrapper(self.env, val, item)
319
320     def __delitem__(self, item):
321         UserDict.__delitem__(self, item)
322         delattr(self.env, item)
323
324     def update(self, dict):
325         for i, v in dict.items():
326             self.__setitem__(i, v)
327
328
329
330 _is_valid_var = re.compile(r'[_a-zA-Z]\w*$')
331
332 def is_valid_construction_var(varstr):
333     """Return if the specified string is a legitimate construction
334     variable.
335     """
336     return _is_valid_var.match(varstr)
337
338
339
340 class SubstitutionEnvironment:
341     """Base class for different flavors of construction environments.
342
343     This class contains a minimal set of methods that handle contruction
344     variable expansion and conversion of strings to Nodes, which may or
345     may not be actually useful as a stand-alone class.  Which methods
346     ended up in this class is pretty arbitrary right now.  They're
347     basically the ones which we've empirically determined are common to
348     the different construction environment subclasses, and most of the
349     others that use or touch the underlying dictionary of construction
350     variables.
351
352     Eventually, this class should contain all the methods that we
353     determine are necessary for a "minimal" interface to the build engine.
354     A full "native Python" SCons environment has gotten pretty heavyweight
355     with all of the methods and Tools and construction variables we've
356     jammed in there, so it would be nice to have a lighter weight
357     alternative for interfaces that don't need all of the bells and
358     whistles.  (At some point, we'll also probably rename this class
359     "Base," since that more reflects what we want this class to become,
360     but because we've released comments that tell people to subclass
361     Environment.Base to create their own flavors of construction
362     environment, we'll save that for a future refactoring when this
363     class actually becomes useful.)
364     """
365
366     if SCons.Memoize.use_memoizer:
367         __metaclass__ = SCons.Memoize.Memoized_Metaclass
368
369     def __init__(self, **kw):
370         """Initialization of an underlying SubstitutionEnvironment class.
371         """
372         if __debug__: logInstanceCreation(self, 'Environment.SubstitutionEnvironment')
373         self.fs = SCons.Node.FS.get_default_fs()
374         self.ans = SCons.Node.Alias.default_ans
375         self.lookup_list = SCons.Node.arg2nodes_lookups
376         self._dict = kw.copy()
377         self._init_special()
378         self.added_methods = []
379         #self._memo = {}
380
381     def _init_special(self):
382         """Initial the dispatch tables for special handling of
383         special construction variables."""
384         self._special_del = {}
385         self._special_del['SCANNERS'] = _del_SCANNERS
386
387         self._special_set = {}
388         for key in reserved_construction_var_names:
389             self._special_set[key] = _set_reserved
390         for key in future_reserved_construction_var_names:
391             self._special_set[key] = _set_future_reserved
392         self._special_set['BUILDERS'] = _set_BUILDERS
393         self._special_set['SCANNERS'] = _set_SCANNERS
394
395         # Freeze the keys of self._special_set in a list for use by
396         # methods that need to check.  (Empirically, list scanning has
397         # gotten better than dict.has_key() in Python 2.5.)
398         self._special_set_keys = self._special_set.keys()
399
400     def __cmp__(self, other):
401         return cmp(self._dict, other._dict)
402
403     def __delitem__(self, key):
404         special = self._special_del.get(key)
405         if special:
406             special(self, key)
407         else:
408             del self._dict[key]
409
410     def __getitem__(self, key):
411         return self._dict[key]
412
413     def __setitem__(self, key, value):
414         # This is heavily used.  This implementation is the best we have
415         # according to the timings in bench/env.__setitem__.py.
416         #
417         # The "key in self._special_set_keys" test here seems to perform
418         # pretty well for the number of keys we have.  A hard-coded
419         # list works a little better in Python 2.5, but that has the
420         # disadvantage of maybe getting out of sync if we ever add more
421         # variable names.  Using self._special_set.has_key() works a
422         # little better in Python 2.4, but is worse then this test.
423         # So right now it seems like a good trade-off, but feel free to
424         # revisit this with bench/env.__setitem__.py as needed (and
425         # as newer versions of Python come out).
426         if key in self._special_set_keys:
427             self._special_set[key](self, key, value)
428         else:
429             # If we already have the entry, then it's obviously a valid
430             # key and we don't need to check.  If we do check, using a
431             # global, pre-compiled regular expression directly is more
432             # efficient than calling another function or a method.
433             if key not in self._dict \
434                and not _is_valid_var.match(key):
435                     raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
436             self._dict[key] = value
437
438     def get(self, key, default=None):
439         """Emulates the get() method of dictionaries."""
440         return self._dict.get(key, default)
441
442     def has_key(self, key):
443         return key in self._dict
444
445     def __contains__(self, key):
446         return self._dict.__contains__(key)
447
448     def items(self):
449         return self._dict.items()
450
451     def arg2nodes(self, args, node_factory=_null, lookup_list=_null, **kw):
452         if node_factory is _null:
453             node_factory = self.fs.File
454         if lookup_list is _null:
455             lookup_list = self.lookup_list
456
457         if not args:
458             return []
459
460         args = SCons.Util.flatten(args)
461
462         nodes = []
463         for v in args:
464             if SCons.Util.is_String(v):
465                 n = None
466                 for l in lookup_list:
467                     n = l(v)
468                     if n is not None:
469                         break
470                 if n is not None:
471                     if SCons.Util.is_String(n):
472                         # n = self.subst(n, raw=1, **kw)
473                         kw['raw'] = 1
474                         n = self.subst(n, **kw)
475                         if node_factory:
476                             n = node_factory(n)
477                     if SCons.Util.is_List(n):
478                         nodes.extend(n)
479                     else:
480                         nodes.append(n)
481                 elif node_factory:
482                     # v = node_factory(self.subst(v, raw=1, **kw))
483                     kw['raw'] = 1
484                     v = node_factory(self.subst(v, **kw))
485                     if SCons.Util.is_List(v):
486                         nodes.extend(v)
487                     else:
488                         nodes.append(v)
489             else:
490                 nodes.append(v)
491
492         return nodes
493
494     def gvars(self):
495         return self._dict
496
497     def lvars(self):
498         return {}
499
500     def subst(self, string, raw=0, target=None, source=None, conv=None, executor=None):
501         """Recursively interpolates construction variables from the
502         Environment into the specified string, returning the expanded
503         result.  Construction variables are specified by a $ prefix
504         in the string and begin with an initial underscore or
505         alphabetic character followed by any number of underscores
506         or alphanumeric characters.  The construction variable names
507         may be surrounded by curly braces to separate the name from
508         trailing characters.
509         """
510         gvars = self.gvars()
511         lvars = self.lvars()
512         lvars['__env__'] = self
513         if executor:
514             lvars.update(executor.get_lvars())
515         return SCons.Subst.scons_subst(string, self, raw, target, source, gvars, lvars, conv)
516
517     def subst_kw(self, kw, raw=0, target=None, source=None):
518         nkw = {}
519         for k, v in kw.items():
520             k = self.subst(k, raw, target, source)
521             if SCons.Util.is_String(v):
522                 v = self.subst(v, raw, target, source)
523             nkw[k] = v
524         return nkw
525
526     def subst_list(self, string, raw=0, target=None, source=None, conv=None, executor=None):
527         """Calls through to SCons.Subst.scons_subst_list().  See
528         the documentation for that function."""
529         gvars = self.gvars()
530         lvars = self.lvars()
531         lvars['__env__'] = self
532         if executor:
533             lvars.update(executor.get_lvars())
534         return SCons.Subst.scons_subst_list(string, self, raw, target, source, gvars, lvars, conv)
535
536     def subst_path(self, path, target=None, source=None):
537         """Substitute a path list, turning EntryProxies into Nodes
538         and leaving Nodes (and other objects) as-is."""
539
540         if not SCons.Util.is_List(path):
541             path = [path]
542
543         def s(obj):
544             """This is the "string conversion" routine that we have our
545             substitutions use to return Nodes, not strings.  This relies
546             on the fact that an EntryProxy object has a get() method that
547             returns the underlying Node that it wraps, which is a bit of
548             architectural dependence that we might need to break or modify
549             in the future in response to additional requirements."""
550             try:
551                 get = obj.get
552             except AttributeError:
553                 obj = SCons.Util.to_String_for_subst(obj)
554             else:
555                 obj = get()
556             return obj
557
558         r = []
559         for p in path:
560             if SCons.Util.is_String(p):
561                 p = self.subst(p, target=target, source=source, conv=s)
562                 if SCons.Util.is_List(p):
563                     if len(p) == 1:
564                         p = p[0]
565                     else:
566                         # We have an object plus a string, or multiple
567                         # objects that we need to smush together.  No choice
568                         # but to make them into a string.
569                         p = ''.join(map(SCons.Util.to_String_for_subst, p))
570             else:
571                 p = s(p)
572             r.append(p)
573         return r
574
575     subst_target_source = subst
576
577     def backtick(self, command):
578         import subprocess
579         # common arguments
580         kw = { 'stdin' : 'devnull',
581                'stdout' : subprocess.PIPE,
582                'stderr' : subprocess.PIPE,
583                'universal_newlines' : True,
584              }
585         # if the command is a list, assume it's been quoted
586         # othewise force a shell
587         if not SCons.Util.is_List(command): kw['shell'] = True
588         # run constructed command
589         #TODO(1.5) p = SCons.Action._subproc(self, command, **kw)
590         p = SCons.Action._subproc(self, command, **kw)
591         out,err = p.communicate()
592         status = p.wait()
593         if err:
594             sys.stderr.write(unicode(err))
595         if status:
596             raise OSError("'%s' exited %d" % (command, status))
597         return out
598
599     def AddMethod(self, function, name=None):
600         """
601         Adds the specified function as a method of this construction
602         environment with the specified name.  If the name is omitted,
603         the default name is the name of the function itself.
604         """
605         method = MethodWrapper(self, function, name)
606         self.added_methods.append(method)
607
608     def RemoveMethod(self, function):
609         """
610         Removes the specified function's MethodWrapper from the
611         added_methods list, so we don't re-bind it when making a clone.
612         """
613         self.added_methods = [dm for dm in self.added_methods if not dm.method is function]
614
615     def Override(self, overrides):
616         """
617         Produce a modified environment whose variables are overriden by
618         the overrides dictionaries.  "overrides" is a dictionary that
619         will override the variables of this environment.
620
621         This function is much more efficient than Clone() or creating
622         a new Environment because it doesn't copy the construction
623         environment dictionary, it just wraps the underlying construction
624         environment, and doesn't even create a wrapper object if there
625         are no overrides.
626         """
627         if not overrides: return self
628         o = copy_non_reserved_keywords(overrides)
629         if not o: return self
630         overrides = {}
631         merges = None
632         for key, value in o.items():
633             if key == 'parse_flags':
634                 merges = value
635             else:
636                 overrides[key] = SCons.Subst.scons_subst_once(value, self, key)
637         env = OverrideEnvironment(self, overrides)
638         if merges: env.MergeFlags(merges)
639         return env
640
641     def ParseFlags(self, *flags):
642         """
643         Parse the set of flags and return a dict with the flags placed
644         in the appropriate entry.  The flags are treated as a typical
645         set of command-line flags for a GNU-like toolchain and used to
646         populate the entries in the dict immediately below.  If one of
647         the flag strings begins with a bang (exclamation mark), it is
648         assumed to be a command and the rest of the string is executed;
649         the result of that evaluation is then added to the dict.
650         """
651         dict = {
652             'ASFLAGS'       : SCons.Util.CLVar(''),
653             'CFLAGS'        : SCons.Util.CLVar(''),
654             'CCFLAGS'       : SCons.Util.CLVar(''),
655             'CPPDEFINES'    : [],
656             'CPPFLAGS'      : SCons.Util.CLVar(''),
657             'CPPPATH'       : [],
658             'FRAMEWORKPATH' : SCons.Util.CLVar(''),
659             'FRAMEWORKS'    : SCons.Util.CLVar(''),
660             'LIBPATH'       : [],
661             'LIBS'          : [],
662             'LINKFLAGS'     : SCons.Util.CLVar(''),
663             'RPATH'         : [],
664         }
665
666         # The use of the "me" parameter to provide our own name for
667         # recursion is an egregious hack to support Python 2.1 and before.
668         def do_parse(arg, me, self = self, dict = dict):
669             # if arg is a sequence, recurse with each element
670             if not arg:
671                 return
672
673             if not SCons.Util.is_String(arg):
674                 for t in arg: me(t, me)
675                 return
676
677             # if arg is a command, execute it
678             if arg[0] == '!':
679                 arg = self.backtick(arg[1:])
680
681             # utility function to deal with -D option
682             def append_define(name, dict = dict):
683                 t = name.split('=')
684                 if len(t) == 1:
685                     dict['CPPDEFINES'].append(name)
686                 else:
687                     dict['CPPDEFINES'].append([t[0], '='.join(t[1:])])
688
689             # Loop through the flags and add them to the appropriate option.
690             # This tries to strike a balance between checking for all possible
691             # flags and keeping the logic to a finite size, so it doesn't
692             # check for some that don't occur often.  It particular, if the
693             # flag is not known to occur in a config script and there's a way
694             # of passing the flag to the right place (by wrapping it in a -W
695             # flag, for example) we don't check for it.  Note that most
696             # preprocessor options are not handled, since unhandled options
697             # are placed in CCFLAGS, so unless the preprocessor is invoked
698             # separately, these flags will still get to the preprocessor.
699             # Other options not currently handled:
700             #  -iqoutedir      (preprocessor search path)
701             #  -u symbol       (linker undefined symbol)
702             #  -s              (linker strip files)
703             #  -static*        (linker static binding)
704             #  -shared*        (linker dynamic binding)
705             #  -symbolic       (linker global binding)
706             #  -R dir          (deprecated linker rpath)
707             # IBM compilers may also accept -qframeworkdir=foo
708     
709             params = shlex.split(arg)
710             append_next_arg_to = None   # for multi-word args
711             for arg in params:
712                 if append_next_arg_to:
713                    if append_next_arg_to == 'CPPDEFINES':
714                        append_define(arg)
715                    elif append_next_arg_to == '-include':
716                        t = ('-include', self.fs.File(arg))
717                        dict['CCFLAGS'].append(t)
718                    elif append_next_arg_to == '-isysroot':
719                        t = ('-isysroot', arg)
720                        dict['CCFLAGS'].append(t)
721                        dict['LINKFLAGS'].append(t)
722                    elif append_next_arg_to == '-arch':
723                        t = ('-arch', arg)
724                        dict['CCFLAGS'].append(t)
725                        dict['LINKFLAGS'].append(t)
726                    else:
727                        dict[append_next_arg_to].append(arg)
728                    append_next_arg_to = None
729                 elif not arg[0] in ['-', '+']:
730                     dict['LIBS'].append(self.fs.File(arg))
731                 elif arg[:2] == '-L':
732                     if arg[2:]:
733                         dict['LIBPATH'].append(arg[2:])
734                     else:
735                         append_next_arg_to = 'LIBPATH'
736                 elif arg[:2] == '-l':
737                     if arg[2:]:
738                         dict['LIBS'].append(arg[2:])
739                     else:
740                         append_next_arg_to = 'LIBS'
741                 elif arg[:2] == '-I':
742                     if arg[2:]:
743                         dict['CPPPATH'].append(arg[2:])
744                     else:
745                         append_next_arg_to = 'CPPPATH'
746                 elif arg[:4] == '-Wa,':
747                     dict['ASFLAGS'].append(arg[4:])
748                     dict['CCFLAGS'].append(arg)
749                 elif arg[:4] == '-Wl,':
750                     if arg[:11] == '-Wl,-rpath=':
751                         dict['RPATH'].append(arg[11:])
752                     elif arg[:7] == '-Wl,-R,':
753                         dict['RPATH'].append(arg[7:])
754                     elif arg[:6] == '-Wl,-R':
755                         dict['RPATH'].append(arg[6:])
756                     else:
757                         dict['LINKFLAGS'].append(arg)
758                 elif arg[:4] == '-Wp,':
759                     dict['CPPFLAGS'].append(arg)
760                 elif arg[:2] == '-D':
761                     if arg[2:]:
762                         append_define(arg[2:])
763                     else:
764                         append_next_arg_to = 'CPPDEFINES'
765                 elif arg == '-framework':
766                     append_next_arg_to = 'FRAMEWORKS'
767                 elif arg[:14] == '-frameworkdir=':
768                     dict['FRAMEWORKPATH'].append(arg[14:])
769                 elif arg[:2] == '-F':
770                     if arg[2:]:
771                         dict['FRAMEWORKPATH'].append(arg[2:])
772                     else:
773                         append_next_arg_to = 'FRAMEWORKPATH'
774                 elif arg == '-mno-cygwin':
775                     dict['CCFLAGS'].append(arg)
776                     dict['LINKFLAGS'].append(arg)
777                 elif arg == '-mwindows':
778                     dict['LINKFLAGS'].append(arg)
779                 elif arg == '-pthread':
780                     dict['CCFLAGS'].append(arg)
781                     dict['LINKFLAGS'].append(arg)
782                 elif arg[:5] == '-std=':
783                     dict['CFLAGS'].append(arg) # C only
784                 elif arg[0] == '+':
785                     dict['CCFLAGS'].append(arg)
786                     dict['LINKFLAGS'].append(arg)
787                 elif arg in ['-include', '-isysroot', '-arch']:
788                     append_next_arg_to = arg
789                 else:
790                     dict['CCFLAGS'].append(arg)
791     
792         for arg in flags:
793             do_parse(arg, do_parse)
794         return dict
795
796     def MergeFlags(self, args, unique=1, dict=None):
797         """
798         Merge the dict in args into the construction variables of this
799         env, or the passed-in dict.  If args is not a dict, it is
800         converted into a dict using ParseFlags.  If unique is not set,
801         the flags are appended rather than merged.
802         """
803
804         if dict is None:
805             dict = self
806         if not SCons.Util.is_Dict(args):
807             args = self.ParseFlags(args)
808         if not unique:
809             self.Append(**args)
810             return self
811         for key, value in args.items():
812             if not value:
813                 continue
814             try:
815                 orig = self[key]
816             except KeyError:
817                 orig = value
818             else:
819                 if not orig:
820                     orig = value
821                 elif value:
822                     # Add orig and value.  The logic here was lifted from
823                     # part of env.Append() (see there for a lot of comments
824                     # about the order in which things are tried) and is
825                     # used mainly to handle coercion of strings to CLVar to
826                     # "do the right thing" given (e.g.) an original CCFLAGS
827                     # string variable like '-pipe -Wall'.
828                     try:
829                         orig = orig + value
830                     except (KeyError, TypeError):
831                         try:
832                             add_to_orig = orig.append
833                         except AttributeError:
834                             value.insert(0, orig)
835                             orig = value
836                         else:
837                             add_to_orig(value)
838             t = []
839             if key[-4:] == 'PATH':
840                 ### keep left-most occurence
841                 for v in orig:
842                     if v not in t:
843                         t.append(v)
844             else:
845                 ### keep right-most occurence
846                 orig.reverse()
847                 for v in orig:
848                     if v not in t:
849                         t.insert(0, v)
850             self[key] = t
851         return self
852
853 #     def MergeShellPaths(self, args, prepend=1):
854 #         """
855 #         Merge the dict in args into the shell environment in env['ENV'].  
856 #         Shell path elements are appended or prepended according to prepend.
857
858 #         Uses Pre/AppendENVPath, so it always appends or prepends uniquely.
859
860 #         Example: env.MergeShellPaths({'LIBPATH': '/usr/local/lib'})
861 #         prepends /usr/local/lib to env['ENV']['LIBPATH'].
862 #         """
863
864 #         for pathname, pathval in args.items():
865 #             if not pathval:
866 #                 continue
867 #             if prepend:
868 #                 self.PrependENVPath(pathname, pathval)
869 #             else:
870 #                 self.AppendENVPath(pathname, pathval)
871
872
873 # Used by the FindSourceFiles() method, below.
874 # Stuck here for support of pre-2.2 Python versions.
875 def build_source(ss, result):
876     for s in ss:
877         if isinstance(s, SCons.Node.FS.Dir):
878             build_source(s.all_children(), result)
879         elif s.has_builder():
880             build_source(s.sources, result)
881         elif isinstance(s.disambiguate(), SCons.Node.FS.File):
882             result.append(s)
883
884 def default_decide_source(dependency, target, prev_ni):
885     f = SCons.Defaults.DefaultEnvironment().decide_source
886     return f(dependency, target, prev_ni)
887
888 def default_decide_target(dependency, target, prev_ni):
889     f = SCons.Defaults.DefaultEnvironment().decide_target
890     return f(dependency, target, prev_ni)
891
892 def default_copy_from_cache(src, dst):
893     f = SCons.Defaults.DefaultEnvironment().copy_from_cache
894     return f(src, dst)
895
896 class Base(SubstitutionEnvironment):
897     """Base class for "real" construction Environments.  These are the
898     primary objects used to communicate dependency and construction
899     information to the build engine.
900
901     Keyword arguments supplied when the construction Environment
902     is created are construction variables used to initialize the
903     Environment.
904     """
905
906     memoizer_counters = []
907
908     #######################################################################
909     # This is THE class for interacting with the SCons build engine,
910     # and it contains a lot of stuff, so we're going to try to keep this
911     # a little organized by grouping the methods.
912     #######################################################################
913
914     #######################################################################
915     # Methods that make an Environment act like a dictionary.  These have
916     # the expected standard names for Python mapping objects.  Note that
917     # we don't actually make an Environment a subclass of UserDict for
918     # performance reasons.  Note also that we only supply methods for
919     # dictionary functionality that we actually need and use.
920     #######################################################################
921
922     def __init__(self,
923                  platform=None,
924                  tools=None,
925                  toolpath=None,
926                  variables=None,
927                  parse_flags = None,
928                  **kw):
929         """
930         Initialization of a basic SCons construction environment,
931         including setting up special construction variables like BUILDER,
932         PLATFORM, etc., and searching for and applying available Tools.
933
934         Note that we do *not* call the underlying base class
935         (SubsitutionEnvironment) initialization, because we need to
936         initialize things in a very specific order that doesn't work
937         with the much simpler base class initialization.
938         """
939         if __debug__: logInstanceCreation(self, 'Environment.Base')
940         self._memo = {}
941         self.fs = SCons.Node.FS.get_default_fs()
942         self.ans = SCons.Node.Alias.default_ans
943         self.lookup_list = SCons.Node.arg2nodes_lookups
944         self._dict = semi_deepcopy(SCons.Defaults.ConstructionEnvironment)
945         self._init_special()
946         self.added_methods = []
947
948         # We don't use AddMethod, or define these as methods in this
949         # class, because we *don't* want these functions to be bound
950         # methods.  They need to operate independently so that the
951         # settings will work properly regardless of whether a given
952         # target ends up being built with a Base environment or an
953         # OverrideEnvironment or what have you.
954         self.decide_target = default_decide_target
955         self.decide_source = default_decide_source
956
957         self.copy_from_cache = default_copy_from_cache
958
959         self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
960
961         if platform is None:
962             platform = self._dict.get('PLATFORM', None)
963             if platform is None:
964                 platform = SCons.Platform.Platform()
965         if SCons.Util.is_String(platform):
966             platform = SCons.Platform.Platform(platform)
967         self._dict['PLATFORM'] = str(platform)
968         platform(self)
969         
970         self._dict['HOST_OS']      = self._dict.get('HOST_OS',None)
971         self._dict['HOST_ARCH']    = self._dict.get('HOST_ARCH',None)
972         
973         # Now set defaults for TARGET_{OS|ARCH}
974         self._dict['TARGET_OS']      = self._dict.get('HOST_OS',None)
975         self._dict['TARGET_ARCH']    = self._dict.get('HOST_ARCH',None)
976         
977
978         # Apply the passed-in and customizable variables to the
979         # environment before calling the tools, because they may use
980         # some of them during initialization.
981         if 'options' in kw:
982             # Backwards compatibility:  they may stll be using the
983             # old "options" keyword.
984             variables = kw['options']
985             del kw['options']
986         self.Replace(**kw)
987         keys = kw.keys()
988         if variables:
989             keys = keys + variables.keys()
990             variables.Update(self)
991
992         save = {}
993         for k in keys:
994             try:
995                 save[k] = self._dict[k]
996             except KeyError:
997                 # No value may have been set if they tried to pass in a
998                 # reserved variable name like TARGETS.
999                 pass
1000
1001         SCons.Tool.Initializers(self)
1002
1003         if tools is None:
1004             tools = self._dict.get('TOOLS', None)
1005             if tools is None:
1006                 tools = ['default']
1007         apply_tools(self, tools, toolpath)
1008
1009         # Now restore the passed-in and customized variables
1010         # to the environment, since the values the user set explicitly
1011         # should override any values set by the tools.
1012         for key, val in save.items():
1013             self._dict[key] = val
1014
1015         # Finally, apply any flags to be merged in
1016         if parse_flags: self.MergeFlags(parse_flags)
1017
1018     #######################################################################
1019     # Utility methods that are primarily for internal use by SCons.
1020     # These begin with lower-case letters.
1021     #######################################################################
1022
1023     def get_builder(self, name):
1024         """Fetch the builder with the specified name from the environment.
1025         """
1026         try:
1027             return self._dict['BUILDERS'][name]
1028         except KeyError:
1029             return None
1030
1031     def get_CacheDir(self):
1032         try:
1033             path = self._CacheDir_path
1034         except AttributeError:
1035             path = SCons.Defaults.DefaultEnvironment()._CacheDir_path
1036         try:
1037             if path == self._last_CacheDir_path:
1038                 return self._last_CacheDir
1039         except AttributeError:
1040             pass
1041         cd = SCons.CacheDir.CacheDir(path)
1042         self._last_CacheDir_path = path
1043         self._last_CacheDir = cd
1044         return cd
1045
1046     def get_factory(self, factory, default='File'):
1047         """Return a factory function for creating Nodes for this
1048         construction environment.
1049         """
1050         name = default
1051         try:
1052             is_node = issubclass(factory, SCons.Node.FS.Base)
1053         except TypeError:
1054             # The specified factory isn't a Node itself--it's
1055             # most likely None, or possibly a callable.
1056             pass
1057         else:
1058             if is_node:
1059                 # The specified factory is a Node (sub)class.  Try to
1060                 # return the FS method that corresponds to the Node's
1061                 # name--that is, we return self.fs.Dir if they want a Dir,
1062                 # self.fs.File for a File, etc.
1063                 try: name = factory.__name__
1064                 except AttributeError: pass
1065                 else: factory = None
1066         if not factory:
1067             # They passed us None, or we picked up a name from a specified
1068             # class, so return the FS method.  (Note that we *don't*
1069             # use our own self.{Dir,File} methods because that would
1070             # cause env.subst() to be called twice on the file name,
1071             # interfering with files that have $$ in them.)
1072             factory = getattr(self.fs, name)
1073         return factory
1074
1075     memoizer_counters.append(SCons.Memoize.CountValue('_gsm'))
1076
1077     def _gsm(self):
1078         try:
1079             return self._memo['_gsm']
1080         except KeyError:
1081             pass
1082
1083         result = {}
1084
1085         try:
1086             scanners = self._dict['SCANNERS']
1087         except KeyError:
1088             pass
1089         else:
1090             # Reverse the scanner list so that, if multiple scanners
1091             # claim they can scan the same suffix, earlier scanners
1092             # in the list will overwrite later scanners, so that
1093             # the result looks like a "first match" to the user.
1094             if not SCons.Util.is_List(scanners):
1095                 scanners = [scanners]
1096             else:
1097                 scanners = scanners[:] # copy so reverse() doesn't mod original
1098             scanners.reverse()
1099             for scanner in scanners:
1100                 for k in scanner.get_skeys(self):
1101                     if k and self['PLATFORM'] == 'win32':
1102                         k = k.lower()
1103                     result[k] = scanner
1104
1105         self._memo['_gsm'] = result
1106
1107         return result
1108
1109     def get_scanner(self, skey):
1110         """Find the appropriate scanner given a key (usually a file suffix).
1111         """
1112         if skey and self['PLATFORM'] == 'win32':
1113             skey = skey.lower()
1114         return self._gsm().get(skey)
1115
1116     def scanner_map_delete(self, kw=None):
1117         """Delete the cached scanner map (if we need to).
1118         """
1119         try:
1120             del self._memo['_gsm']
1121         except KeyError:
1122             pass
1123
1124     def _update(self, dict):
1125         """Update an environment's values directly, bypassing the normal
1126         checks that occur when users try to set items.
1127         """
1128         self._dict.update(dict)
1129
1130     def get_src_sig_type(self):
1131         try:
1132             return self.src_sig_type
1133         except AttributeError:
1134             t = SCons.Defaults.DefaultEnvironment().src_sig_type
1135             self.src_sig_type = t
1136             return t
1137
1138     def get_tgt_sig_type(self):
1139         try:
1140             return self.tgt_sig_type
1141         except AttributeError:
1142             t = SCons.Defaults.DefaultEnvironment().tgt_sig_type
1143             self.tgt_sig_type = t
1144             return t
1145
1146     #######################################################################
1147     # Public methods for manipulating an Environment.  These begin with
1148     # upper-case letters.  The essential characteristic of methods in
1149     # this section is that they do *not* have corresponding same-named
1150     # global functions.  For example, a stand-alone Append() function
1151     # makes no sense, because Append() is all about appending values to
1152     # an Environment's construction variables.
1153     #######################################################################
1154
1155     def Append(self, **kw):
1156         """Append values to existing construction variables
1157         in an Environment.
1158         """
1159         kw = copy_non_reserved_keywords(kw)
1160         for key, val in kw.items():
1161             # It would be easier on the eyes to write this using
1162             # "continue" statements whenever we finish processing an item,
1163             # but Python 1.5.2 apparently doesn't let you use "continue"
1164             # within try:-except: blocks, so we have to nest our code.
1165             try:
1166                 orig = self._dict[key]
1167             except KeyError:
1168                 # No existing variable in the environment, so just set
1169                 # it to the new value.
1170                 self._dict[key] = val
1171             else:
1172                 try:
1173                     # Check if the original looks like a dictionary.
1174                     # If it is, we can't just try adding the value because
1175                     # dictionaries don't have __add__() methods, and
1176                     # things like UserList will incorrectly coerce the
1177                     # original dict to a list (which we don't want).
1178                     update_dict = orig.update
1179                 except AttributeError:
1180                     try:
1181                         # Most straightforward:  just try to add them
1182                         # together.  This will work in most cases, when the
1183                         # original and new values are of compatible types.
1184                         self._dict[key] = orig + val
1185                     except (KeyError, TypeError):
1186                         try:
1187                             # Check if the original is a list.
1188                             add_to_orig = orig.append
1189                         except AttributeError:
1190                             # The original isn't a list, but the new
1191                             # value is (by process of elimination),
1192                             # so insert the original in the new value
1193                             # (if there's one to insert) and replace
1194                             # the variable with it.
1195                             if orig:
1196                                 val.insert(0, orig)
1197                             self._dict[key] = val
1198                         else:
1199                             # The original is a list, so append the new
1200                             # value to it (if there's a value to append).
1201                             if val:
1202                                 add_to_orig(val)
1203                 else:
1204                     # The original looks like a dictionary, so update it
1205                     # based on what we think the value looks like.
1206                     if SCons.Util.is_List(val):
1207                         for v in val:
1208                             orig[v] = None
1209                     else:
1210                         try:
1211                             update_dict(val)
1212                         except (AttributeError, TypeError, ValueError):
1213                             if SCons.Util.is_Dict(val):
1214                                 for k, v in val.items():
1215                                     orig[k] = v
1216                             else:
1217                                 orig[val] = None
1218         self.scanner_map_delete(kw)
1219
1220     # allow Dirs and strings beginning with # for top-relative
1221     # Note this uses the current env's fs (in self).
1222     def _canonicalize(self, path):
1223         if not SCons.Util.is_String(path): # typically a Dir
1224             path = str(path)
1225         if path and path[0] == '#':
1226             path = str(self.fs.Dir(path))
1227         return path
1228
1229     def AppendENVPath(self, name, newpath, envname = 'ENV', 
1230                       sep = os.pathsep, delete_existing=1):
1231         """Append path elements to the path 'name' in the 'ENV'
1232         dictionary for this environment.  Will only add any particular
1233         path once, and will normpath and normcase all paths to help
1234         assure this.  This can also handle the case where the env
1235         variable is a list instead of a string.
1236
1237         If delete_existing is 0, a newpath which is already in the path
1238         will not be moved to the end (it will be left where it is).
1239         """
1240
1241         orig = ''
1242         if envname in self._dict and name in self._dict[envname]:
1243             orig = self._dict[envname][name]
1244
1245         nv = SCons.Util.AppendPath(orig, newpath, sep, delete_existing,
1246                                    canonicalize=self._canonicalize)
1247
1248         if envname not in self._dict:
1249             self._dict[envname] = {}
1250
1251         self._dict[envname][name] = nv
1252
1253     def AppendUnique(self, delete_existing=0, **kw):
1254         """Append values to existing construction variables
1255         in an Environment, if they're not already there.
1256         If delete_existing is 1, removes existing values first, so
1257         values move to end.
1258         """
1259         kw = copy_non_reserved_keywords(kw)
1260         for key, val in kw.items():
1261             if SCons.Util.is_List(val):
1262                 val = _delete_duplicates(val, delete_existing)
1263             if key not in self._dict or self._dict[key] in ('', None):
1264                 self._dict[key] = val
1265             elif SCons.Util.is_Dict(self._dict[key]) and \
1266                  SCons.Util.is_Dict(val):
1267                 self._dict[key].update(val)
1268             elif SCons.Util.is_List(val):
1269                 dk = self._dict[key]
1270                 if not SCons.Util.is_List(dk):
1271                     dk = [dk]
1272                 if delete_existing:
1273                     dk = [x for x in dk if x not in val]
1274                 else:
1275                     val = [x for x in val if x not in dk]
1276                 self._dict[key] = dk + val
1277             else:
1278                 dk = self._dict[key]
1279                 if SCons.Util.is_List(dk):
1280                     # By elimination, val is not a list.  Since dk is a
1281                     # list, wrap val in a list first.
1282                     if delete_existing:
1283                         dk = [x for x in dk if x not in val]
1284                         self._dict[key] = dk + [val]
1285                     else:
1286                         if not val in dk:
1287                             self._dict[key] = dk + [val]
1288                 else:
1289                     if delete_existing:
1290                         dk = [x for x in dk if x not in val]
1291                     self._dict[key] = dk + val
1292         self.scanner_map_delete(kw)
1293
1294     def Clone(self, tools=[], toolpath=None, parse_flags = None, **kw):
1295         """Return a copy of a construction Environment.  The
1296         copy is like a Python "deep copy"--that is, independent
1297         copies are made recursively of each objects--except that
1298         a reference is copied when an object is not deep-copyable
1299         (like a function).  There are no references to any mutable
1300         objects in the original Environment.
1301         """
1302         clone = copy.copy(self)
1303         clone._dict = semi_deepcopy(self._dict)
1304
1305         try:
1306             cbd = clone._dict['BUILDERS']
1307         except KeyError:
1308             pass
1309         else:
1310             clone._dict['BUILDERS'] = BuilderDict(cbd, clone)
1311
1312         # Check the methods added via AddMethod() and re-bind them to
1313         # the cloned environment.  Only do this if the attribute hasn't
1314         # been overwritten by the user explicitly and still points to
1315         # the added method.
1316         clone.added_methods = []
1317         for mw in self.added_methods:
1318             if mw == getattr(self, mw.name):
1319                 clone.added_methods.append(mw.clone(clone))
1320
1321         clone._memo = {}
1322
1323         # Apply passed-in variables before the tools
1324         # so the tools can use the new variables
1325         kw = copy_non_reserved_keywords(kw)
1326         new = {}
1327         for key, value in kw.items():
1328             new[key] = SCons.Subst.scons_subst_once(value, self, key)
1329         clone.Replace(**new)
1330
1331         apply_tools(clone, tools, toolpath)
1332
1333         # apply them again in case the tools overwrote them
1334         clone.Replace(**new)        
1335
1336         # Finally, apply any flags to be merged in
1337         if parse_flags: clone.MergeFlags(parse_flags)
1338
1339         if __debug__: logInstanceCreation(self, 'Environment.EnvironmentClone')
1340         return clone
1341
1342     def Copy(self, *args, **kw):
1343         global _warn_copy_deprecated
1344         if _warn_copy_deprecated:
1345             msg = "The env.Copy() method is deprecated; use the env.Clone() method instead."
1346             SCons.Warnings.warn(SCons.Warnings.DeprecatedCopyWarning, msg)
1347             _warn_copy_deprecated = False
1348         return self.Clone(*args, **kw)
1349
1350     def _changed_build(self, dependency, target, prev_ni):
1351         if dependency.changed_state(target, prev_ni):
1352             return 1
1353         return self.decide_source(dependency, target, prev_ni)
1354
1355     def _changed_content(self, dependency, target, prev_ni):
1356         return dependency.changed_content(target, prev_ni)
1357
1358     def _changed_source(self, dependency, target, prev_ni):
1359         target_env = dependency.get_build_env()
1360         type = target_env.get_tgt_sig_type()
1361         if type == 'source':
1362             return target_env.decide_source(dependency, target, prev_ni)
1363         else:
1364             return target_env.decide_target(dependency, target, prev_ni)
1365
1366     def _changed_timestamp_then_content(self, dependency, target, prev_ni):
1367         return dependency.changed_timestamp_then_content(target, prev_ni)
1368
1369     def _changed_timestamp_newer(self, dependency, target, prev_ni):
1370         return dependency.changed_timestamp_newer(target, prev_ni)
1371
1372     def _changed_timestamp_match(self, dependency, target, prev_ni):
1373         return dependency.changed_timestamp_match(target, prev_ni)
1374
1375     def _copy_from_cache(self, src, dst):
1376         return self.fs.copy(src, dst)
1377
1378     def _copy2_from_cache(self, src, dst):
1379         return self.fs.copy2(src, dst)
1380
1381     def Decider(self, function):
1382         copy_function = self._copy2_from_cache
1383         if function in ('MD5', 'content'):
1384             if not SCons.Util.md5:
1385                 raise UserError("MD5 signatures are not available in this version of Python.")
1386             function = self._changed_content
1387         elif function == 'MD5-timestamp':
1388             function = self._changed_timestamp_then_content
1389         elif function in ('timestamp-newer', 'make'):
1390             function = self._changed_timestamp_newer
1391             copy_function = self._copy_from_cache
1392         elif function == 'timestamp-match':
1393             function = self._changed_timestamp_match
1394         elif not callable(function):
1395             raise UserError("Unknown Decider value %s" % repr(function))
1396
1397         # We don't use AddMethod because we don't want to turn the
1398         # function, which only expects three arguments, into a bound
1399         # method, which would add self as an initial, fourth argument.
1400         self.decide_target = function
1401         self.decide_source = function
1402
1403         self.copy_from_cache = copy_function
1404
1405     def Detect(self, progs):
1406         """Return the first available program in progs.
1407         """
1408         if not SCons.Util.is_List(progs):
1409             progs = [ progs ]
1410         for prog in progs:
1411             path = self.WhereIs(prog)
1412             if path: return prog
1413         return None
1414
1415     def Dictionary(self, *args):
1416         if not args:
1417             return self._dict
1418         dlist = [self._dict[x] for x in args]
1419         if len(dlist) == 1:
1420             dlist = dlist[0]
1421         return dlist
1422
1423     def Dump(self, key = None):
1424         """
1425         Using the standard Python pretty printer, dump the contents of the
1426         scons build environment to stdout.
1427
1428         If the key passed in is anything other than None, then that will
1429         be used as an index into the build environment dictionary and
1430         whatever is found there will be fed into the pretty printer. Note
1431         that this key is case sensitive.
1432         """
1433         import pprint
1434         pp = pprint.PrettyPrinter(indent=2)
1435         if key:
1436             dict = self.Dictionary(key)
1437         else:
1438             dict = self.Dictionary()
1439         return pp.pformat(dict)
1440
1441     def FindIxes(self, paths, prefix, suffix):
1442         """
1443         Search a list of paths for something that matches the prefix and suffix.
1444
1445         paths - the list of paths or nodes.
1446         prefix - construction variable for the prefix.
1447         suffix - construction variable for the suffix.
1448         """
1449
1450         suffix = self.subst('$'+suffix)
1451         prefix = self.subst('$'+prefix)
1452
1453         for path in paths:
1454             dir,name = os.path.split(str(path))
1455             if name[:len(prefix)] == prefix and name[-len(suffix):] == suffix:
1456                 return path
1457
1458     def ParseConfig(self, command, function=None, unique=1):
1459         """
1460         Use the specified function to parse the output of the command
1461         in order to modify the current environment.  The 'command' can
1462         be a string or a list of strings representing a command and
1463         its arguments.  'Function' is an optional argument that takes
1464         the environment, the output of the command, and the unique flag.
1465         If no function is specified, MergeFlags, which treats the output
1466         as the result of a typical 'X-config' command (i.e. gtk-config),
1467         will merge the output into the appropriate variables.
1468         """
1469         if function is None:
1470             def parse_conf(env, cmd, unique=unique):
1471                 return env.MergeFlags(cmd, unique)
1472             function = parse_conf
1473         if SCons.Util.is_List(command):
1474             command = ' '.join(command)
1475         command = self.subst(command)
1476         return function(self, self.backtick(command))
1477
1478     def ParseDepends(self, filename, must_exist=None, only_one=0):
1479         """
1480         Parse a mkdep-style file for explicit dependencies.  This is
1481         completely abusable, and should be unnecessary in the "normal"
1482         case of proper SCons configuration, but it may help make
1483         the transition from a Make hierarchy easier for some people
1484         to swallow.  It can also be genuinely useful when using a tool
1485         that can write a .d file, but for which writing a scanner would
1486         be too complicated.
1487         """
1488         filename = self.subst(filename)
1489         try:
1490             fp = open(filename, 'r')
1491         except IOError:
1492             if must_exist:
1493                 raise
1494             return
1495         lines = SCons.Util.LogicalLines(fp).readlines()
1496         lines = [l for l in lines if l[0] != '#']
1497         tdlist = []
1498         for line in lines:
1499             try:
1500                 target, depends = line.split(':', 1)
1501             except (AttributeError, TypeError, ValueError):
1502                 # Python 1.5.2 throws TypeError if line isn't a string,
1503                 # Python 2.x throws AttributeError because it tries
1504                 # to call line.split().  Either can throw ValueError
1505                 # if the line doesn't split into two or more elements.
1506                 pass
1507             else:
1508                 tdlist.append((target.split(), depends.split()))
1509         if only_one:
1510             targets = reduce(lambda x, y: x+y, [p[0] for p in tdlist])
1511             if len(targets) > 1:
1512                 raise SCons.Errors.UserError(
1513                             "More than one dependency target found in `%s':  %s"
1514                                             % (filename, targets))
1515         for target, depends in tdlist:
1516             self.Depends(target, depends)
1517
1518     def Platform(self, platform):
1519         platform = self.subst(platform)
1520         return SCons.Platform.Platform(platform)(self)
1521
1522     def Prepend(self, **kw):
1523         """Prepend values to existing construction variables
1524         in an Environment.
1525         """
1526         kw = copy_non_reserved_keywords(kw)
1527         for key, val in kw.items():
1528             # It would be easier on the eyes to write this using
1529             # "continue" statements whenever we finish processing an item,
1530             # but Python 1.5.2 apparently doesn't let you use "continue"
1531             # within try:-except: blocks, so we have to nest our code.
1532             try:
1533                 orig = self._dict[key]
1534             except KeyError:
1535                 # No existing variable in the environment, so just set
1536                 # it to the new value.
1537                 self._dict[key] = val
1538             else:
1539                 try:
1540                     # Check if the original looks like a dictionary.
1541                     # If it is, we can't just try adding the value because
1542                     # dictionaries don't have __add__() methods, and
1543                     # things like UserList will incorrectly coerce the
1544                     # original dict to a list (which we don't want).
1545                     update_dict = orig.update
1546                 except AttributeError:
1547                     try:
1548                         # Most straightforward:  just try to add them
1549                         # together.  This will work in most cases, when the
1550                         # original and new values are of compatible types.
1551                         self._dict[key] = val + orig
1552                     except (KeyError, TypeError):
1553                         try:
1554                             # Check if the added value is a list.
1555                             add_to_val = val.append
1556                         except AttributeError:
1557                             # The added value isn't a list, but the
1558                             # original is (by process of elimination),
1559                             # so insert the the new value in the original
1560                             # (if there's one to insert).
1561                             if val:
1562                                 orig.insert(0, val)
1563                         else:
1564                             # The added value is a list, so append
1565                             # the original to it (if there's a value
1566                             # to append).
1567                             if orig:
1568                                 add_to_val(orig)
1569                             self._dict[key] = val
1570                 else:
1571                     # The original looks like a dictionary, so update it
1572                     # based on what we think the value looks like.
1573                     if SCons.Util.is_List(val):
1574                         for v in val:
1575                             orig[v] = None
1576                     else:
1577                         try:
1578                             update_dict(val)
1579                         except (AttributeError, TypeError, ValueError):
1580                             if SCons.Util.is_Dict(val):
1581                                 for k, v in val.items():
1582                                     orig[k] = v
1583                             else:
1584                                 orig[val] = None
1585         self.scanner_map_delete(kw)
1586
1587     def PrependENVPath(self, name, newpath, envname = 'ENV', sep = os.pathsep,
1588                        delete_existing=1):
1589         """Prepend path elements to the path 'name' in the 'ENV'
1590         dictionary for this environment.  Will only add any particular
1591         path once, and will normpath and normcase all paths to help
1592         assure this.  This can also handle the case where the env
1593         variable is a list instead of a string.
1594
1595         If delete_existing is 0, a newpath which is already in the path
1596         will not be moved to the front (it will be left where it is).
1597         """
1598
1599         orig = ''
1600         if envname in self._dict and name in self._dict[envname]:
1601             orig = self._dict[envname][name]
1602
1603         nv = SCons.Util.PrependPath(orig, newpath, sep, delete_existing,
1604                                     canonicalize=self._canonicalize)
1605
1606         if envname not in self._dict:
1607             self._dict[envname] = {}
1608
1609         self._dict[envname][name] = nv
1610
1611     def PrependUnique(self, delete_existing=0, **kw):
1612         """Prepend values to existing construction variables
1613         in an Environment, if they're not already there.
1614         If delete_existing is 1, removes existing values first, so
1615         values move to front.
1616         """
1617         kw = copy_non_reserved_keywords(kw)
1618         for key, val in kw.items():
1619             if SCons.Util.is_List(val):
1620                 val = _delete_duplicates(val, not delete_existing)
1621             if key not in self._dict or self._dict[key] in ('', None):
1622                 self._dict[key] = val
1623             elif SCons.Util.is_Dict(self._dict[key]) and \
1624                  SCons.Util.is_Dict(val):
1625                 self._dict[key].update(val)
1626             elif SCons.Util.is_List(val):
1627                 dk = self._dict[key]
1628                 if not SCons.Util.is_List(dk):
1629                     dk = [dk]
1630                 if delete_existing:
1631                     dk = [x for x in dk if x not in val]
1632                 else:
1633                     val = [x for x in val if x not in dk]
1634                 self._dict[key] = val + dk
1635             else:
1636                 dk = self._dict[key]
1637                 if SCons.Util.is_List(dk):
1638                     # By elimination, val is not a list.  Since dk is a
1639                     # list, wrap val in a list first.
1640                     if delete_existing:
1641                         dk = [x for x in dk if x not in val]
1642                         self._dict[key] = [val] + dk
1643                     else:
1644                         if not val in dk:
1645                             self._dict[key] = [val] + dk
1646                 else:
1647                     if delete_existing:
1648                         dk = [x for x in dk if x not in val]
1649                     self._dict[key] = val + dk
1650         self.scanner_map_delete(kw)
1651
1652     def Replace(self, **kw):
1653         """Replace existing construction variables in an Environment
1654         with new construction variables and/or values.
1655         """
1656         try:
1657             kwbd = kw['BUILDERS']
1658         except KeyError:
1659             pass
1660         else:
1661             kwbd = semi_deepcopy(kwbd)
1662             del kw['BUILDERS']
1663             self.__setitem__('BUILDERS', kwbd)
1664         kw = copy_non_reserved_keywords(kw)
1665         self._update(semi_deepcopy(kw))
1666         self.scanner_map_delete(kw)
1667
1668     def ReplaceIxes(self, path, old_prefix, old_suffix, new_prefix, new_suffix):
1669         """
1670         Replace old_prefix with new_prefix and old_suffix with new_suffix.
1671
1672         env - Environment used to interpolate variables.
1673         path - the path that will be modified.
1674         old_prefix - construction variable for the old prefix.
1675         old_suffix - construction variable for the old suffix.
1676         new_prefix - construction variable for the new prefix.
1677         new_suffix - construction variable for the new suffix.
1678         """
1679         old_prefix = self.subst('$'+old_prefix)
1680         old_suffix = self.subst('$'+old_suffix)
1681
1682         new_prefix = self.subst('$'+new_prefix)
1683         new_suffix = self.subst('$'+new_suffix)
1684
1685         dir,name = os.path.split(str(path))
1686         if name[:len(old_prefix)] == old_prefix:
1687             name = name[len(old_prefix):]
1688         if name[-len(old_suffix):] == old_suffix:
1689             name = name[:-len(old_suffix)]
1690         return os.path.join(dir, new_prefix+name+new_suffix)
1691
1692     def SetDefault(self, **kw):
1693         for k in kw.keys():
1694             if k in self._dict:
1695                 del kw[k]
1696         self.Replace(**kw)
1697
1698     def _find_toolpath_dir(self, tp):
1699         return self.fs.Dir(self.subst(tp)).srcnode().abspath
1700
1701     def Tool(self, tool, toolpath=None, **kw):
1702         if SCons.Util.is_String(tool):
1703             tool = self.subst(tool)
1704             if toolpath is None:
1705                 toolpath = self.get('toolpath', [])
1706             toolpath = list(map(self._find_toolpath_dir, toolpath))
1707             tool = SCons.Tool.Tool(tool, toolpath, **kw)
1708         tool(self)
1709
1710     def WhereIs(self, prog, path=None, pathext=None, reject=[]):
1711         """Find prog in the path.
1712         """
1713         if path is None:
1714             try:
1715                 path = self['ENV']['PATH']
1716             except KeyError:
1717                 pass
1718         elif SCons.Util.is_String(path):
1719             path = self.subst(path)
1720         if pathext is None:
1721             try:
1722                 pathext = self['ENV']['PATHEXT']
1723             except KeyError:
1724                 pass
1725         elif SCons.Util.is_String(pathext):
1726             pathext = self.subst(pathext)
1727         prog = self.subst(prog)
1728         path = SCons.Util.WhereIs(prog, path, pathext, reject)
1729         if path: return path
1730         return None
1731
1732     #######################################################################
1733     # Public methods for doing real "SCons stuff" (manipulating
1734     # dependencies, setting attributes on targets, etc.).  These begin
1735     # with upper-case letters.  The essential characteristic of methods
1736     # in this section is that they all *should* have corresponding
1737     # same-named global functions.
1738     #######################################################################
1739
1740     def Action(self, *args, **kw):
1741         def subst_string(a, self=self):
1742             if SCons.Util.is_String(a):
1743                 a = self.subst(a)
1744             return a
1745         nargs = list(map(subst_string, args))
1746         nkw = self.subst_kw(kw)
1747         return SCons.Action.Action(*nargs, **nkw)
1748
1749     def AddPreAction(self, files, action):
1750         nodes = self.arg2nodes(files, self.fs.Entry)
1751         action = SCons.Action.Action(action)
1752         uniq = {}
1753         for executor in [n.get_executor() for n in nodes]:
1754             uniq[executor] = 1
1755         for executor in uniq.keys():
1756             executor.add_pre_action(action)
1757         return nodes
1758
1759     def AddPostAction(self, files, action):
1760         nodes = self.arg2nodes(files, self.fs.Entry)
1761         action = SCons.Action.Action(action)
1762         uniq = {}
1763         for executor in [n.get_executor() for n in nodes]:
1764             uniq[executor] = 1
1765         for executor in uniq.keys():
1766             executor.add_post_action(action)
1767         return nodes
1768
1769     def Alias(self, target, source=[], action=None, **kw):
1770         tlist = self.arg2nodes(target, self.ans.Alias)
1771         if not SCons.Util.is_List(source):
1772             source = [source]
1773         source = [_f for _f in source if _f]
1774
1775         if not action:
1776             if not source:
1777                 # There are no source files and no action, so just
1778                 # return a target list of classic Alias Nodes, without
1779                 # any builder.  The externally visible effect is that
1780                 # this will make the wrapping Script.BuildTask class
1781                 # say that there's "Nothing to be done" for this Alias,
1782                 # instead of that it's "up to date."
1783                 return tlist
1784
1785             # No action, but there are sources.  Re-call all the target
1786             # builders to add the sources to each target.
1787             result = []
1788             for t in tlist:
1789                 bld = t.get_builder(AliasBuilder)
1790                 result.extend(bld(self, t, source))
1791             return result
1792
1793         nkw = self.subst_kw(kw)
1794         nkw.update({
1795             'action'            : SCons.Action.Action(action),
1796             'source_factory'    : self.fs.Entry,
1797             'multi'             : 1,
1798             'is_explicit'       : None,
1799         })
1800         bld = SCons.Builder.Builder(**nkw)
1801
1802         # Apply the Builder separately to each target so that the Aliases
1803         # stay separate.  If we did one "normal" Builder call with the
1804         # whole target list, then all of the target Aliases would be
1805         # associated under a single Executor.
1806         result = []
1807         for t in tlist:
1808             # Calling the convert() method will cause a new Executor to be
1809             # created from scratch, so we have to explicitly initialize
1810             # it with the target's existing sources, plus our new ones,
1811             # so nothing gets lost.
1812             b = t.get_builder()
1813             if b is None or b is AliasBuilder:
1814                 b = bld
1815             else:
1816                 nkw['action'] = b.action + action
1817                 b = SCons.Builder.Builder(**nkw)
1818             t.convert()
1819             result.extend(b(self, t, t.sources + source))
1820         return result
1821
1822     def AlwaysBuild(self, *targets):
1823         tlist = []
1824         for t in targets:
1825             tlist.extend(self.arg2nodes(t, self.fs.Entry))
1826         for t in tlist:
1827             t.set_always_build()
1828         return tlist
1829
1830     def BuildDir(self, *args, **kw):
1831         if 'build_dir' in kw:
1832             kw['variant_dir'] = kw['build_dir']
1833             del kw['build_dir']
1834         return self.VariantDir(*args, **kw)
1835
1836     def Builder(self, **kw):
1837         nkw = self.subst_kw(kw)
1838         return SCons.Builder.Builder(**nkw)
1839
1840     def CacheDir(self, path):
1841         import SCons.CacheDir
1842         if path is not None:
1843             path = self.subst(path)
1844         self._CacheDir_path = path
1845
1846     def Clean(self, targets, files):
1847         global CleanTargets
1848         tlist = self.arg2nodes(targets, self.fs.Entry)
1849         flist = self.arg2nodes(files, self.fs.Entry)
1850         for t in tlist:
1851             try:
1852                 CleanTargets[t].extend(flist)
1853             except KeyError:
1854                 CleanTargets[t] = flist
1855
1856     def Configure(self, *args, **kw):
1857         nargs = [self]
1858         if args:
1859             nargs = nargs + self.subst_list(args)[0]
1860         nkw = self.subst_kw(kw)
1861         nkw['_depth'] = kw.get('_depth', 0) + 1
1862         try:
1863             nkw['custom_tests'] = self.subst_kw(nkw['custom_tests'])
1864         except KeyError:
1865             pass
1866         return SCons.SConf.SConf(*nargs, **nkw)
1867
1868     def Command(self, target, source, action, **kw):
1869         """Builds the supplied target files from the supplied
1870         source files using the supplied action.  Action may
1871         be any type that the Builder constructor will accept
1872         for an action."""
1873         bkw = {
1874             'action' : action,
1875             'target_factory' : self.fs.Entry,
1876             'source_factory' : self.fs.Entry,
1877         }
1878         try: bkw['source_scanner'] = kw['source_scanner']
1879         except KeyError: pass
1880         else: del kw['source_scanner']
1881         bld = SCons.Builder.Builder(**bkw)
1882         return bld(self, target, source, **kw)
1883
1884     def Depends(self, target, dependency):
1885         """Explicity specify that 'target's depend on 'dependency'."""
1886         tlist = self.arg2nodes(target, self.fs.Entry)
1887         dlist = self.arg2nodes(dependency, self.fs.Entry)
1888         for t in tlist:
1889             t.add_dependency(dlist)
1890         return tlist
1891
1892     def Dir(self, name, *args, **kw):
1893         """
1894         """
1895         s = self.subst(name)
1896         if SCons.Util.is_Sequence(s):
1897             result=[]
1898             for e in s:
1899                 result.append(self.fs.Dir(e, *args, **kw))
1900             return result
1901         return self.fs.Dir(s, *args, **kw)
1902
1903     def NoClean(self, *targets):
1904         """Tags a target so that it will not be cleaned by -c"""
1905         tlist = []
1906         for t in targets:
1907             tlist.extend(self.arg2nodes(t, self.fs.Entry))
1908         for t in tlist:
1909             t.set_noclean()
1910         return tlist
1911
1912     def NoCache(self, *targets):
1913         """Tags a target so that it will not be cached"""
1914         tlist = []
1915         for t in targets:
1916             tlist.extend(self.arg2nodes(t, self.fs.Entry))
1917         for t in tlist:
1918             t.set_nocache()
1919         return tlist
1920
1921     def Entry(self, name, *args, **kw):
1922         """
1923         """
1924         s = self.subst(name)
1925         if SCons.Util.is_Sequence(s):
1926             result=[]
1927             for e in s:
1928                 result.append(self.fs.Entry(e, *args, **kw))
1929             return result
1930         return self.fs.Entry(s, *args, **kw)
1931
1932     def Environment(self, **kw):
1933         return SCons.Environment.Environment(**self.subst_kw(kw))
1934
1935     def Execute(self, action, *args, **kw):
1936         """Directly execute an action through an Environment
1937         """
1938         action = self.Action(action, *args, **kw)
1939         result = action([], [], self)
1940         if isinstance(result, SCons.Errors.BuildError):
1941             errstr = result.errstr
1942             if result.filename:
1943                 errstr = result.filename + ': ' + errstr
1944             sys.stderr.write("scons: *** %s\n" % errstr)
1945             return result.status
1946         else:
1947             return result
1948
1949     def File(self, name, *args, **kw):
1950         """
1951         """
1952         s = self.subst(name)
1953         if SCons.Util.is_Sequence(s):
1954             result=[]
1955             for e in s:
1956                 result.append(self.fs.File(e, *args, **kw))
1957             return result
1958         return self.fs.File(s, *args, **kw)
1959
1960     def FindFile(self, file, dirs):
1961         file = self.subst(file)
1962         nodes = self.arg2nodes(dirs, self.fs.Dir)
1963         return SCons.Node.FS.find_file(file, tuple(nodes))
1964
1965     def Flatten(self, sequence):
1966         return SCons.Util.flatten(sequence)
1967
1968     def GetBuildPath(self, files):
1969         result = list(map(str, self.arg2nodes(files, self.fs.Entry)))
1970         if SCons.Util.is_List(files):
1971             return result
1972         else:
1973             return result[0]
1974
1975     def Glob(self, pattern, ondisk=True, source=False, strings=False):
1976         return self.fs.Glob(self.subst(pattern), ondisk, source, strings)
1977
1978     def Ignore(self, target, dependency):
1979         """Ignore a dependency."""
1980         tlist = self.arg2nodes(target, self.fs.Entry)
1981         dlist = self.arg2nodes(dependency, self.fs.Entry)
1982         for t in tlist:
1983             t.add_ignore(dlist)
1984         return tlist
1985
1986     def Literal(self, string):
1987         return SCons.Subst.Literal(string)
1988
1989     def Local(self, *targets):
1990         ret = []
1991         for targ in targets:
1992             if isinstance(targ, SCons.Node.Node):
1993                 targ.set_local()
1994                 ret.append(targ)
1995             else:
1996                 for t in self.arg2nodes(targ, self.fs.Entry):
1997                    t.set_local()
1998                    ret.append(t)
1999         return ret
2000
2001     def Precious(self, *targets):
2002         tlist = []
2003         for t in targets:
2004             tlist.extend(self.arg2nodes(t, self.fs.Entry))
2005         for t in tlist:
2006             t.set_precious()
2007         return tlist
2008
2009     def Repository(self, *dirs, **kw):
2010         dirs = self.arg2nodes(list(dirs), self.fs.Dir)
2011         self.fs.Repository(*dirs, **kw)
2012
2013     def Requires(self, target, prerequisite):
2014         """Specify that 'prerequisite' must be built before 'target',
2015         (but 'target' does not actually depend on 'prerequisite'
2016         and need not be rebuilt if it changes)."""
2017         tlist = self.arg2nodes(target, self.fs.Entry)
2018         plist = self.arg2nodes(prerequisite, self.fs.Entry)
2019         for t in tlist:
2020             t.add_prerequisite(plist)
2021         return tlist
2022
2023     def Scanner(self, *args, **kw):
2024         nargs = []
2025         for arg in args:
2026             if SCons.Util.is_String(arg):
2027                 arg = self.subst(arg)
2028             nargs.append(arg)
2029         nkw = self.subst_kw(kw)
2030         return SCons.Scanner.Base(*nargs, **nkw)
2031
2032     def SConsignFile(self, name=".sconsign", dbm_module=None):
2033         if name is not None:
2034             name = self.subst(name)
2035             if not os.path.isabs(name):
2036                 name = os.path.join(str(self.fs.SConstruct_dir), name)
2037         if name:
2038             name = os.path.normpath(name)
2039             sconsign_dir = os.path.dirname(name)
2040             if sconsign_dir and not os.path.exists(sconsign_dir):
2041                 self.Execute(SCons.Defaults.Mkdir(sconsign_dir))
2042         SCons.SConsign.File(name, dbm_module)
2043
2044     def SideEffect(self, side_effect, target):
2045         """Tell scons that side_effects are built as side
2046         effects of building targets."""
2047         side_effects = self.arg2nodes(side_effect, self.fs.Entry)
2048         targets = self.arg2nodes(target, self.fs.Entry)
2049
2050         for side_effect in side_effects:
2051             if side_effect.multiple_side_effect_has_builder():
2052                 raise SCons.Errors.UserError("Multiple ways to build the same target were specified for: %s" % str(side_effect))
2053             side_effect.add_source(targets)
2054             side_effect.side_effect = 1
2055             self.Precious(side_effect)
2056             for target in targets:
2057                 target.side_effects.append(side_effect)
2058         return side_effects
2059
2060     def SourceCode(self, entry, builder):
2061         """Arrange for a source code builder for (part of) a tree."""
2062         entries = self.arg2nodes(entry, self.fs.Entry)
2063         for entry in entries:
2064             entry.set_src_builder(builder)
2065         return entries
2066
2067     def SourceSignatures(self, type):
2068         global _warn_source_signatures_deprecated
2069         if _warn_source_signatures_deprecated:
2070             msg = "The env.SourceSignatures() method is deprecated;\n" + \
2071                   "\tconvert your build to use the env.Decider() method instead."
2072             SCons.Warnings.warn(SCons.Warnings.DeprecatedSourceSignaturesWarning, msg)
2073             _warn_source_signatures_deprecated = False
2074         type = self.subst(type)
2075         self.src_sig_type = type
2076         if type == 'MD5':
2077             if not SCons.Util.md5:
2078                 raise UserError("MD5 signatures are not available in this version of Python.")
2079             self.decide_source = self._changed_content
2080         elif type == 'timestamp':
2081             self.decide_source = self._changed_timestamp_match
2082         else:
2083             raise UserError("Unknown source signature type '%s'" % type)
2084
2085     def Split(self, arg):
2086         """This function converts a string or list into a list of strings
2087         or Nodes.  This makes things easier for users by allowing files to
2088         be specified as a white-space separated list to be split.
2089         The input rules are:
2090             - A single string containing names separated by spaces. These will be
2091               split apart at the spaces.
2092             - A single Node instance
2093             - A list containing either strings or Node instances. Any strings
2094               in the list are not split at spaces.
2095         In all cases, the function returns a list of Nodes and strings."""
2096         if SCons.Util.is_List(arg):
2097             return list(map(self.subst, arg))
2098         elif SCons.Util.is_String(arg):
2099             return self.subst(arg).split()
2100         else:
2101             return [self.subst(arg)]
2102
2103     def TargetSignatures(self, type):
2104         global _warn_target_signatures_deprecated
2105         if _warn_target_signatures_deprecated:
2106             msg = "The env.TargetSignatures() method is deprecated;\n" + \
2107                   "\tconvert your build to use the env.Decider() method instead."
2108             SCons.Warnings.warn(SCons.Warnings.DeprecatedTargetSignaturesWarning, msg)
2109             _warn_target_signatures_deprecated = False
2110         type = self.subst(type)
2111         self.tgt_sig_type = type
2112         if type in ('MD5', 'content'):
2113             if not SCons.Util.md5:
2114                 raise UserError("MD5 signatures are not available in this version of Python.")
2115             self.decide_target = self._changed_content
2116         elif type == 'timestamp':
2117             self.decide_target = self._changed_timestamp_match
2118         elif type == 'build':
2119             self.decide_target = self._changed_build
2120         elif type == 'source':
2121             self.decide_target = self._changed_source
2122         else:
2123             raise UserError("Unknown target signature type '%s'"%type)
2124
2125     def Value(self, value, built_value=None):
2126         """
2127         """
2128         return SCons.Node.Python.Value(value, built_value)
2129
2130     def VariantDir(self, variant_dir, src_dir, duplicate=1):
2131         variant_dir = self.arg2nodes(variant_dir, self.fs.Dir)[0]
2132         src_dir = self.arg2nodes(src_dir, self.fs.Dir)[0]
2133         self.fs.VariantDir(variant_dir, src_dir, duplicate)
2134
2135     def FindSourceFiles(self, node='.'):
2136         """ returns a list of all source files.
2137         """
2138         node = self.arg2nodes(node, self.fs.Entry)[0]
2139
2140         sources = []
2141         # Uncomment this and get rid of the global definition when we
2142         # drop support for pre-2.2 Python versions.
2143         #def build_source(ss, result):
2144         #    for s in ss:
2145         #        if isinstance(s, SCons.Node.FS.Dir):
2146         #            build_source(s.all_children(), result)
2147         #        elif s.has_builder():
2148         #            build_source(s.sources, result)
2149         #        elif isinstance(s.disambiguate(), SCons.Node.FS.File):
2150         #            result.append(s)
2151         build_source(node.all_children(), sources)
2152
2153     # THIS CODE APPEARS TO HAVE NO EFFECT
2154     #    # get the final srcnode for all nodes, this means stripping any
2155     #    # attached build node by calling the srcnode function
2156     #    for file in sources:
2157     #        srcnode = file.srcnode()
2158     #        while srcnode != file.srcnode():
2159     #            srcnode = file.srcnode()
2160
2161         # remove duplicates
2162         return list(set(sources))
2163
2164     def FindInstalledFiles(self):
2165         """ returns the list of all targets of the Install and InstallAs Builder.
2166         """
2167         from SCons.Tool import install
2168         if install._UNIQUE_INSTALLED_FILES is None:
2169             install._UNIQUE_INSTALLED_FILES = SCons.Util.uniquer_hashables(install._INSTALLED_FILES)
2170         return install._UNIQUE_INSTALLED_FILES
2171
2172 class OverrideEnvironment(Base):
2173     """A proxy that overrides variables in a wrapped construction
2174     environment by returning values from an overrides dictionary in
2175     preference to values from the underlying subject environment.
2176
2177     This is a lightweight (I hope) proxy that passes through most use of
2178     attributes to the underlying Environment.Base class, but has just
2179     enough additional methods defined to act like a real construction
2180     environment with overridden values.  It can wrap either a Base
2181     construction environment, or another OverrideEnvironment, which
2182     can in turn nest arbitrary OverrideEnvironments...
2183
2184     Note that we do *not* call the underlying base class
2185     (SubsitutionEnvironment) initialization, because we get most of those
2186     from proxying the attributes of the subject construction environment.
2187     But because we subclass SubstitutionEnvironment, this class also
2188     has inherited arg2nodes() and subst*() methods; those methods can't
2189     be proxied because they need *this* object's methods to fetch the
2190     values from the overrides dictionary.
2191     """
2192
2193     def __init__(self, subject, overrides={}):
2194         if __debug__: logInstanceCreation(self, 'Environment.OverrideEnvironment')
2195         self.__dict__['__subject'] = subject
2196         self.__dict__['overrides'] = overrides
2197
2198     # Methods that make this class act like a proxy.
2199     def __getattr__(self, name):
2200         return getattr(self.__dict__['__subject'], name)
2201     def __setattr__(self, name, value):
2202         setattr(self.__dict__['__subject'], name, value)
2203
2204     # Methods that make this class act like a dictionary.
2205     def __getitem__(self, key):
2206         try:
2207             return self.__dict__['overrides'][key]
2208         except KeyError:
2209             return self.__dict__['__subject'].__getitem__(key)
2210     def __setitem__(self, key, value):
2211         if not is_valid_construction_var(key):
2212             raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
2213         self.__dict__['overrides'][key] = value
2214     def __delitem__(self, key):
2215         try:
2216             del self.__dict__['overrides'][key]
2217         except KeyError:
2218             deleted = 0
2219         else:
2220             deleted = 1
2221         try:
2222             result = self.__dict__['__subject'].__delitem__(key)
2223         except KeyError:
2224             if not deleted:
2225                 raise
2226             result = None
2227         return result
2228     def get(self, key, default=None):
2229         """Emulates the get() method of dictionaries."""
2230         try:
2231             return self.__dict__['overrides'][key]
2232         except KeyError:
2233             return self.__dict__['__subject'].get(key, default)
2234     def has_key(self, key):
2235         try:
2236             self.__dict__['overrides'][key]
2237             return 1
2238         except KeyError:
2239             return key in self.__dict__['__subject']
2240     def __contains__(self, key):
2241         if self.__dict__['overrides'].__contains__(key):
2242             return 1
2243         return self.__dict__['__subject'].__contains__(key)
2244     def Dictionary(self):
2245         """Emulates the items() method of dictionaries."""
2246         d = self.__dict__['__subject'].Dictionary().copy()
2247         d.update(self.__dict__['overrides'])
2248         return d
2249     def items(self):
2250         """Emulates the items() method of dictionaries."""
2251         return self.Dictionary().items()
2252
2253     # Overridden private construction environment methods.
2254     def _update(self, dict):
2255         """Update an environment's values directly, bypassing the normal
2256         checks that occur when users try to set items.
2257         """
2258         self.__dict__['overrides'].update(dict)
2259
2260     def gvars(self):
2261         return self.__dict__['__subject'].gvars()
2262
2263     def lvars(self):
2264         lvars = self.__dict__['__subject'].lvars()
2265         lvars.update(self.__dict__['overrides'])
2266         return lvars
2267
2268     # Overridden public construction environment methods.
2269     def Replace(self, **kw):
2270         kw = copy_non_reserved_keywords(kw)
2271         self.__dict__['overrides'].update(semi_deepcopy(kw))
2272
2273 # The entry point that will be used by the external world
2274 # to refer to a construction environment.  This allows the wrapper
2275 # interface to extend a construction environment for its own purposes
2276 # by subclassing SCons.Environment.Base and then assigning the
2277 # class to SCons.Environment.Environment.
2278
2279 Environment = Base
2280
2281 # An entry point for returning a proxy subclass instance that overrides
2282 # the subst*() methods so they don't actually perform construction
2283 # variable substitution.  This is specifically intended to be the shim
2284 # layer in between global function calls (which don't want construction
2285 # variable substitution) and the DefaultEnvironment() (which would
2286 # substitute variables if left to its own devices)."""
2287 #
2288 # We have to wrap this in a function that allows us to delay definition of
2289 # the class until it's necessary, so that when it subclasses Environment
2290 # it will pick up whatever Environment subclass the wrapper interface
2291 # might have assigned to SCons.Environment.Environment.
2292
2293 def NoSubstitutionProxy(subject):
2294     class _NoSubstitutionProxy(Environment):
2295         def __init__(self, subject):
2296             self.__dict__['__subject'] = subject
2297         def __getattr__(self, name):
2298             return getattr(self.__dict__['__subject'], name)
2299         def __setattr__(self, name, value):
2300             return setattr(self.__dict__['__subject'], name, value)
2301         def raw_to_mode(self, dict):
2302             try:
2303                 raw = dict['raw']
2304             except KeyError:
2305                 pass
2306             else:
2307                 del dict['raw']
2308                 dict['mode'] = raw
2309         def subst(self, string, *args, **kwargs):
2310             return string
2311         def subst_kw(self, kw, *args, **kwargs):
2312             return kw
2313         def subst_list(self, string, *args, **kwargs):
2314             nargs = (string, self,) + args
2315             nkw = kwargs.copy()
2316             nkw['gvars'] = {}
2317             self.raw_to_mode(nkw)
2318             return SCons.Subst.scons_subst_list(*nargs, **nkw)
2319         def subst_target_source(self, string, *args, **kwargs):
2320             nargs = (string, self,) + args
2321             nkw = kwargs.copy()
2322             nkw['gvars'] = {}
2323             self.raw_to_mode(nkw)
2324             return SCons.Subst.scons_subst(*nargs, **nkw)
2325     return _NoSubstitutionProxy(subject)
2326
2327 # Local Variables:
2328 # tab-width:4
2329 # indent-tabs-mode:nil
2330 # End:
2331 # vim: set expandtab tabstop=4 shiftwidth=4: