Add Fortran COMSTR variables for output customizability.
[scons.git] / src / engine / SCons / Environment.py
1 """SCons.Environment
2
3 Base class for construction Environments.  These are
4 the primary objects used to communicate dependency and
5 construction information to the build engine.
6
7 Keyword arguments supplied when the construction Environment
8 is created are construction variables used to initialize the
9 Environment 
10 """
11
12 #
13 # __COPYRIGHT__
14 #
15 # Permission is hereby granted, free of charge, to any person obtaining
16 # a copy of this software and associated documentation files (the
17 # "Software"), to deal in the Software without restriction, including
18 # without limitation the rights to use, copy, modify, merge, publish,
19 # distribute, sublicense, and/or sell copies of the Software, and to
20 # permit persons to whom the Software is furnished to do so, subject to
21 # the following conditions:
22 #
23 # The above copyright notice and this permission notice shall be included
24 # in all copies or substantial portions of the Software.
25 #
26 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
27 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
28 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 #
34
35 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
36
37
38 import copy
39 import os
40 import os.path
41 import string
42 from UserDict import UserDict
43
44 import SCons.Action
45 import SCons.Builder
46 from SCons.Debug import logInstanceCreation
47 import SCons.Defaults
48 import SCons.Errors
49 import SCons.Node
50 import SCons.Node.Alias
51 import SCons.Node.FS
52 import SCons.Node.Python
53 import SCons.Platform
54 import SCons.SConsign
55 import SCons.Sig
56 import SCons.Sig.MD5
57 import SCons.Sig.TimeStamp
58 import SCons.Tool
59 import SCons.Util
60 import SCons.Warnings
61
62 class _Null:
63     pass
64
65 _null = _Null
66
67 CleanTargets = {}
68 CalculatorArgs = {}
69
70 # Pull UserError into the global name space for the benefit of
71 # Environment().SourceSignatures(), which has some import statements
72 # which seem to mess up its ability to reference SCons directly.
73 UserError = SCons.Errors.UserError
74
75 def installFunc(target, source, env):
76     """Install a source file into a target using the function specified
77     as the INSTALL construction variable."""
78     try:
79         install = env['INSTALL']
80     except KeyError:
81         raise SCons.Errors.UserError('Missing INSTALL construction variable.')
82     return install(target[0].path, source[0].path, env)
83
84 def installString(target, source, env):
85     return 'Install file: "%s" as "%s"' % (source[0], target[0])
86
87 installAction = SCons.Action.Action(installFunc, installString)
88
89 InstallBuilder = SCons.Builder.Builder(action=installAction,
90                                        name='InstallBuilder')
91
92 def alias_builder(env, target, source):
93     pass
94
95 AliasBuilder = SCons.Builder.Builder(action = alias_builder,
96                                      target_factory = SCons.Node.Alias.default_ans.Alias,
97                                      source_factory = SCons.Node.FS.default_fs.Entry,
98                                      multi = 1,
99                                      is_explicit = None,
100                                      name='AliasBuilder')
101
102 def our_deepcopy(x):
103    """deepcopy lists and dictionaries, and just copy the reference
104    for everything else."""
105    if SCons.Util.is_Dict(x):
106        copy = {}
107        for key in x.keys():
108            copy[key] = our_deepcopy(x[key])
109    elif SCons.Util.is_List(x):
110        copy = map(our_deepcopy, x)
111        try:
112            copy = x.__class__(copy)
113        except AttributeError:
114            pass
115    else:
116        copy = x
117    return copy
118
119 def apply_tools(env, tools, toolpath):
120     if not tools:
121         return
122     # Filter out null tools from the list.
123     for tool in filter(None, tools):
124         if SCons.Util.is_List(tool) or type(tool)==type(()):
125             toolname = tool[0]
126             toolargs = tool[1] # should be a dict of kw args
127             tool = apply(env.Tool, (toolname, toolpath), toolargs)
128         else:
129             env.Tool(tool, toolpath)
130
131 # These names are controlled by SCons; users should never set or override
132 # them.  This warning can optionally be turned off, but scons will still
133 # ignore the illegal variable names even if it's off.
134 reserved_construction_var_names = \
135     ['TARGET', 'TARGETS', 'SOURCE', 'SOURCES']
136
137 def copy_non_reserved_keywords(dict):
138     result = our_deepcopy(dict)
139     for k in result.keys():
140         if k in reserved_construction_var_names:
141             SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning,
142                                 "Ignoring attempt to set reserved variable `%s'" % k)
143             del result[k]
144     return result
145
146 class BuilderWrapper:
147     """Wrapper class that associates an environment with a Builder at
148     instantiation."""
149     def __init__(self, env, builder):
150         self.env = env
151         self.builder = builder
152
153     def __call__(self, target=None, source=_null, *args, **kw):
154         if source is _null:
155             source = target
156             target = None
157         if not target is None and not SCons.Util.is_List(target):
158             target = [target]
159         if not source is None and not SCons.Util.is_List(source):
160             source = [source]
161         return apply(self.builder, (self.env, target, source) + args, kw)
162
163     # This allows a Builder to be executed directly
164     # through the Environment to which it's attached.
165     # In practice, we shouldn't need this, because
166     # builders actually get executed through a Node.
167     # But we do have a unit test for this, and can't
168     # yet rule out that it would be useful in the
169     # future, so leave it for now.
170     def execute(self, **kw):
171         kw['env'] = self.env
172         apply(self.builder.execute, (), kw)
173
174 class BuilderDict(UserDict):
175     """This is a dictionary-like class used by an Environment to hold
176     the Builders.  We need to do this because every time someone changes
177     the Builders in the Environment's BUILDERS dictionary, we must
178     update the Environment's attributes."""
179     def __init__(self, dict, env):
180         # Set self.env before calling the superclass initialization,
181         # because it will end up calling our other methods, which will
182         # need to point the values in this dictionary to self.env.
183         self.env = env
184         UserDict.__init__(self, dict)
185
186     def __setitem__(self, item, val):
187         UserDict.__setitem__(self, item, val)
188         try:
189             self.setenvattr(item, val)
190         except AttributeError:
191             # Have to catch this because sometimes __setitem__ gets
192             # called out of __init__, when we don't have an env
193             # attribute yet, nor do we want one!
194             pass
195
196     def setenvattr(self, item, val):
197         """Set the corresponding environment attribute for this Builder.
198
199         If the value is already a BuilderWrapper, we pull the builder
200         out of it and make another one, so that making a copy of an
201         existing BuilderDict is guaranteed separate wrappers for each
202         Builder + Environment pair."""
203         try:
204             builder = val.builder
205         except AttributeError:
206             builder = val
207         setattr(self.env, item, BuilderWrapper(self.env, builder))
208
209     def __delitem__(self, item):
210         UserDict.__delitem__(self, item)
211         delattr(self.env, item)
212
213     def update(self, dict):
214         for i, v in dict.items():
215             self.__setitem__(i, v)
216
217 class Base:
218     """Base class for construction Environments.  These are
219     the primary objects used to communicate dependency and
220     construction information to the build engine.
221
222     Keyword arguments supplied when the construction Environment
223     is created are construction variables used to initialize the
224     Environment.
225     """
226
227     #######################################################################
228     # This is THE class for interacting with the SCons build engine,
229     # and it contains a lot of stuff, so we're going to try to keep this
230     # a little organized by grouping the methods.
231     #######################################################################
232
233     #######################################################################
234     # Methods that make an Environment act like a dictionary.  These have
235     # the expected standard names for Python mapping objects.  Note that
236     # we don't actually make an Environment a subclass of UserDict for
237     # performance reasons.  Note also that we only supply methods for
238     # dictionary functionality that we actually need and use.
239     #######################################################################
240
241     def __init__(self,
242                  platform=None,
243                  tools=None,
244                  toolpath=[],
245                  options=None,
246                  **kw):
247         if __debug__: logInstanceCreation(self)
248         self.fs = SCons.Node.FS.default_fs
249         self.ans = SCons.Node.Alias.default_ans
250         self.lookup_list = SCons.Node.arg2nodes_lookups
251         self._dict = our_deepcopy(SCons.Defaults.ConstructionEnvironment)
252
253         self._dict['__env__'] = self
254         self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
255
256         if platform is None:
257             platform = self._dict.get('PLATFORM', None)
258             if platform is None:
259                 platform = SCons.Platform.Platform()
260         if SCons.Util.is_String(platform):
261             platform = SCons.Platform.Platform(platform)
262         self._dict['PLATFORM'] = str(platform)
263         platform(self)
264
265         # Apply the passed-in variables before calling the tools,
266         # because they may use some of them:
267         apply(self.Replace, (), kw)
268         
269         # Update the environment with the customizable options
270         # before calling the tools, since they may use some of the options: 
271         if options:
272             options.Update(self)
273
274         if tools is None:
275             tools = self._dict.get('TOOLS', None)
276             if tools is None:
277                 tools = ['default']
278         apply_tools(self, tools, toolpath)
279
280         # Reapply the passed in variables after calling the tools,
281         # since they should overide anything set by the tools:
282         apply(self.Replace, (), kw)
283
284         # Update the environment with the customizable options
285         # after calling the tools, since they should override anything
286         # set by the tools:
287         if options:
288             options.Update(self)
289
290     def __cmp__(self, other):
291         # Since an Environment now has an '__env__' construction variable
292         # that refers to itself, delete that variable to avoid infinite
293         # loops when comparing the underlying dictionaries in some Python
294         # versions (*cough* 1.5.2 *cough*)...
295         sdict = self._dict.copy()
296         del sdict['__env__']
297         odict = other._dict.copy()
298         del odict['__env__']
299         return cmp(sdict, odict)
300
301     def __getitem__(self, key):
302         return self._dict[key]
303
304     def __setitem__(self, key, value):
305         if key in reserved_construction_var_names:
306             SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning,
307                                 "Ignoring attempt to set reserved variable `%s'" % key)
308         elif key == 'BUILDERS':
309             try:
310                 bd = self._dict[key]
311                 for k in bd.keys():
312                     del bd[k]
313             except KeyError:
314                 self._dict[key] = BuilderDict(kwbd, self)
315             self._dict[key].update(value)
316         elif key == 'SCANNERS':
317             self._dict[key] = value
318             self.scanner_map_delete()
319         else:
320             if not SCons.Util.is_valid_construction_var(key):
321                 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
322             self._dict[key] = value
323
324     def __delitem__(self, key):
325         del self._dict[key]
326
327     def items(self):
328         "Emulates the items() method of dictionaries."""
329         return self._dict.items()
330
331     def has_key(self, key):
332         return self._dict.has_key(key)
333
334     def get(self, key, default=None):
335         "Emulates the get() method of dictionaries."""
336         return self._dict.get(key, default)
337
338     #######################################################################
339     # Utility methods that are primarily for internal use by SCons.
340     # These begin with lower-case letters.  Note that the subst() method
341     # is actually already out of the closet and used by people.
342     #######################################################################
343
344     def arg2nodes(self, args, node_factory=_null, lookup_list=_null):
345         if node_factory is _null:
346             node_factory = self.fs.File
347         if lookup_list is _null:
348             lookup_list = self.lookup_list
349
350         if not args:
351             return []
352
353         if SCons.Util.is_List(args):
354             args = SCons.Util.flatten(args)
355         else:
356             args = [args]
357
358         nodes = []
359         for v in args:
360             if SCons.Util.is_String(v):
361                 n = None
362                 for l in lookup_list:
363                     n = l(v)
364                     if not n is None:
365                         break
366                 if not n is None:
367                     if SCons.Util.is_String(n):
368                         n = self.subst(n, raw=1)
369                         if node_factory:
370                             n = node_factory(n)
371                     if SCons.Util.is_List(n):
372                         nodes.extend(n)
373                     else:
374                         nodes.append(n)
375                 elif node_factory:
376                     v = node_factory(self.subst(v, raw=1))
377                     if SCons.Util.is_List(v):
378                         nodes.extend(v)
379                     else:
380                         nodes.append(v)
381             else:
382                 nodes.append(v)
383     
384         return nodes
385
386     def get_calculator(self):
387         try:
388             return self._calculator
389         except AttributeError:
390             try:
391                 module = self._calc_module
392                 c = apply(SCons.Sig.Calculator, (module,), CalculatorArgs)
393             except AttributeError:
394                 # Note that we're calling get_calculator() here, so the
395                 # DefaultEnvironment() must have a _calc_module attribute
396                 # to avoid infinite recursion.
397                 c = SCons.Defaults.DefaultEnvironment().get_calculator()
398             self._calculator = c
399             return c
400
401     def get_builder(self, name):
402         """Fetch the builder with the specified name from the environment.
403         """
404         try:
405             return self._dict['BUILDERS'][name]
406         except KeyError:
407             return None
408
409     def get_scanner(self, skey):
410         """Find the appropriate scanner given a key (usually a file suffix).
411         """
412         try:
413             sm = self.scanner_map
414         except AttributeError:
415             try:
416                 scanners = self._dict['SCANNERS']
417             except KeyError:
418                 self.scanner_map = {}
419                 return None
420             else:
421                 self.scanner_map = sm = {}
422                 # Reverse the scanner list so that, if multiple scanners
423                 # claim they can scan the same suffix, earlier scanners
424                 # in the list will overwrite later scanners, so that
425                 # the result looks like a "first match" to the user.
426                 if not SCons.Util.is_List(scanners):
427                     scanners = [scanners]
428                 scanners.reverse()
429                 for scanner in scanners:
430                     for k in scanner.get_skeys(self):
431                         sm[k] = scanner
432         try:
433             return sm[skey]
434         except KeyError:
435             return None
436
437     def scanner_map_delete(self, kw=None):
438         """Delete the cached scanner map (if we need to).
439         """
440         if not kw is None and not kw.has_key('SCANNERS'):
441             return
442         try:
443             del self.scanner_map
444         except AttributeError:
445             pass
446
447     def subst(self, string, raw=0, target=None, source=None, dict=None, conv=None):
448         """Recursively interpolates construction variables from the
449         Environment into the specified string, returning the expanded
450         result.  Construction variables are specified by a $ prefix
451         in the string and begin with an initial underscore or
452         alphabetic character followed by any number of underscores
453         or alphanumeric characters.  The construction variable names
454         may be surrounded by curly braces to separate the name from
455         trailing characters.
456         """
457         return SCons.Util.scons_subst(string, self, raw, target, source, dict, conv)
458
459     def subst_kw(self, kw, raw=0, target=None, source=None, dict=None):
460         nkw = {}
461         for k, v in kw.items():
462             k = self.subst(k, raw, target, source, dict)
463             if SCons.Util.is_String(v):
464                 v = self.subst(v, raw, target, source, dict)
465             nkw[k] = v
466         return nkw
467
468     def subst_list(self, string, raw=0, target=None, source=None, dict=None, conv=None):
469         """Calls through to SCons.Util.scons_subst_list().  See
470         the documentation for that function."""
471         return SCons.Util.scons_subst_list(string, self, raw, target, source, dict, conv)
472
473
474     def subst_path(self, path):
475         """Substitute a path list, turning EntryProxies into Nodes
476         and leaving Nodes (and other objects) as-is."""
477
478         if not SCons.Util.is_List(path):
479             path = [path]
480
481         def s(obj):
482             """This is the "string conversion" routine that we have our
483             substitutions use to return Nodes, not strings.  This relies
484             on the fact that an EntryProxy object has a get() method that
485             returns the underlying Node that it wraps, which is a bit of
486             architectural dependence that we might need to break or modify
487             in the future in response to additional requirements."""
488             try:
489                 get = obj.get
490             except AttributeError:
491                 pass
492             else:
493                 obj = get()
494             return obj
495
496         r = []
497         for p in path:
498             if SCons.Util.is_String(p):
499                 p = self.subst(p, conv=s)
500                 if SCons.Util.is_List(p):
501                     if len(p) == 1:
502                         p = p[0]
503                     else:
504                         # We have an object plus a string, or multiple
505                         # objects that we need to smush together.  No choice
506                         # but to make them into a string.
507                         p = string.join(map(SCons.Util.to_String, p), '')
508             else:
509                 p = s(p)
510             r.append(p)
511         return r
512
513     subst_target_source = subst
514
515     def _update(self, dict):
516         """Update an environment's values directly, bypassing the normal
517         checks that occur when users try to set items.
518         """
519         self._dict.update(dict)
520
521     def use_build_signature(self):
522         try:
523             return self._build_signature
524         except AttributeError:
525             b = SCons.Defaults.DefaultEnvironment()._build_signature
526             self._build_signature = b
527             return b
528
529     #######################################################################
530     # Public methods for manipulating an Environment.  These begin with
531     # upper-case letters.  The essential characteristic of methods in
532     # this section is that they do *not* have corresponding same-named
533     # global functions.  For example, a stand-alone Append() function
534     # makes no sense, because Append() is all about appending values to
535     # an Environment's construction variables.
536     #######################################################################
537
538     def Append(self, **kw):
539         """Append values to existing construction variables
540         in an Environment.
541         """
542         kw = copy_non_reserved_keywords(kw)
543         for key, val in kw.items():
544             # It would be easier on the eyes to write this using
545             # "continue" statements whenever we finish processing an item,
546             # but Python 1.5.2 apparently doesn't let you use "continue"
547             # within try:-except: blocks, so we have to nest our code.
548             try:
549                 orig = self._dict[key]
550             except KeyError:
551                 # No existing variable in the environment, so just set
552                 # it to the new value.
553                 self._dict[key] = val
554             else:
555                 try:
556                     # Most straightforward:  just try to add them
557                     # together.  This will work in most cases, when the
558                     # original and new values are of compatible types.
559                     self._dict[key] = orig + val
560                 except TypeError:
561                     try:
562                         # Try to update a dictionary value with another.
563                         # If orig isn't a dictionary, it won't have an
564                         # update() method; if val isn't a dictionary,
565                         # it won't have a keys() method.  Either way,
566                         # it's an AttributeError.
567                         orig.update(val)
568                     except AttributeError:
569                         try:
570                             # Check if the original is a list.
571                             add_to_orig = orig.append
572                         except AttributeError:
573                             # The original isn't a list, but the new
574                             # value is (by process of elimination),
575                             # so insert the original in the new value
576                             # (if there's one to insert) and replace
577                             # the variable with it.
578                             if orig:
579                                 val.insert(0, orig)
580                             self._dict[key] = val
581                         else:
582                             # The original is a list, so append the new
583                             # value to it (if there's a value to append).
584                             if val:
585                                 add_to_orig(val)
586         self.scanner_map_delete(kw)
587
588     def AppendENVPath(self, name, newpath, envname = 'ENV', sep = os.pathsep):
589         """Append path elements to the path 'name' in the 'ENV'
590         dictionary for this environment.  Will only add any particular
591         path once, and will normpath and normcase all paths to help
592         assure this.  This can also handle the case where the env
593         variable is a list instead of a string.
594         """
595
596         orig = ''
597         if self._dict.has_key(envname) and self._dict[envname].has_key(name):
598             orig = self._dict[envname][name]
599
600         nv = SCons.Util.AppendPath(orig, newpath, sep)
601             
602         if not self._dict.has_key(envname):
603             self._dict[envname] = {}
604
605         self._dict[envname][name] = nv
606
607     def AppendUnique(self, **kw):
608         """Append values to existing construction variables
609         in an Environment, if they're not already there.
610         """
611         kw = copy_non_reserved_keywords(kw)
612         for key, val in kw.items():
613             if not self._dict.has_key(key):
614                 self._dict[key] = val
615             elif SCons.Util.is_Dict(self._dict[key]) and \
616                  SCons.Util.is_Dict(val):
617                 self._dict[key].update(val)
618             elif SCons.Util.is_List(val):
619                 dk = self._dict[key]
620                 if not SCons.Util.is_List(dk):
621                     dk = [dk]
622                 val = filter(lambda x, dk=dk: x not in dk, val)
623                 self._dict[key] = dk + val
624             else:
625                 dk = self._dict[key]
626                 if SCons.Util.is_List(dk):
627                     if not val in dk:
628                         self._dict[key] = dk + val
629                 else:
630                     self._dict[key] = self._dict[key] + val
631         self.scanner_map_delete(kw)
632
633     def Copy(self, tools=[], toolpath=[], **kw):
634         """Return a copy of a construction Environment.  The
635         copy is like a Python "deep copy"--that is, independent
636         copies are made recursively of each objects--except that
637         a reference is copied when an object is not deep-copyable
638         (like a function).  There are no references to any mutable
639         objects in the original Environment.
640         """
641         clone = copy.copy(self)
642         clone._dict = our_deepcopy(self._dict)
643         clone['__env__'] = clone
644         try:
645             cbd = clone._dict['BUILDERS']
646             clone._dict['BUILDERS'] = BuilderDict(cbd, clone)
647         except KeyError:
648             pass
649         
650         apply_tools(clone, tools, toolpath)
651
652         # Apply passed-in variables after the new tools.
653         kw = copy_non_reserved_keywords(kw)
654         new = {}
655         for key, value in kw.items():
656             new[key] = SCons.Util.scons_subst_once(value, self, key)
657         apply(clone.Replace, (), new)
658         if __debug__: logInstanceCreation(self, 'EnvironmentCopy')
659         return clone
660
661     def Detect(self, progs):
662         """Return the first available program in progs.
663         """
664         if not SCons.Util.is_List(progs):
665             progs = [ progs ]
666         for prog in progs:
667             path = self.WhereIs(prog)
668             if path: return prog
669         return None
670
671     def Dictionary(self, *args):
672         if not args:
673             return self._dict
674         dlist = map(lambda x, s=self: s._dict[x], args)
675         if len(dlist) == 1:
676             dlist = dlist[0]
677         return dlist
678
679     def Dump(self, key = None):
680         """
681         Using the standard Python pretty printer, dump the contents of the
682         scons build environment to stdout.
683
684         If the key passed in is anything other than None, then that will
685         be used as an index into the build environment dictionary and
686         whatever is found there will be fed into the pretty printer. Note
687         that this key is case sensitive.
688         """
689         import pprint
690         pp = pprint.PrettyPrinter(indent=2)
691         if key:
692             dict = self.Dictionary(key)
693         else:
694             dict = self.Dictionary()
695         return pp.pformat(dict)
696
697     def FindIxes(self, paths, prefix, suffix):
698         """
699         Search a list of paths for something that matches the prefix and suffix.
700
701         paths - the list of paths or nodes.
702         prefix - construction variable for the prefix.
703         suffix - construction variable for the suffix.
704         """
705
706         suffix = self.subst('$'+suffix)
707         prefix = self.subst('$'+prefix)
708
709         for path in paths:
710             dir,name = os.path.split(str(path))
711             if name[:len(prefix)] == prefix and name[-len(suffix):] == suffix: 
712                 return path
713
714     def Override(self, overrides):
715         """
716         Produce a modified environment whose variables
717         are overriden by the overrides dictionaries.
718
719         overrides - a dictionary that will override
720         the variables of this environment.
721
722         This function is much more efficient than Copy()
723         or creating a new Environment because it doesn't do
724         a deep copy of the dictionary, and doesn't do a copy
725         at all if there are no overrides.
726         """
727
728         if overrides:
729             env = copy.copy(self)
730             env._dict = copy.copy(self._dict)
731             env['__env__'] = env
732             overrides = copy_non_reserved_keywords(overrides)
733             new = {}
734             for key, value in overrides.items():
735                 new[key] = SCons.Util.scons_subst_once(value, self, key)
736             env._dict.update(new)
737             if __debug__: logInstanceCreation(self, 'EnvironmentOverride')
738             return env
739         else:
740             return self
741
742     def ParseConfig(self, command, function=None):
743         """
744         Use the specified function to parse the output of the command
745         in order to modify the current environment. The 'command' can
746         be a string or a list of strings representing a command and
747         it's arguments. 'Function' is an optional argument that takes
748         the environment and the output of the command. If no function is
749         specified, the output will be treated as the output of a typical
750         'X-config' command (i.e. gtk-config) and used to append to the
751         ASFLAGS, CCFLAGS, CPPFLAGS, CPPPATH, LIBPATH, LIBS, LINKFLAGS
752         and CCFLAGS variables.
753         """
754
755         # the default parse function
756         def parse_conf(env, output, fs=self.fs):
757             dict = {
758                 'ASFLAGS'       : [],
759                 'CCFLAGS'       : [],
760                 'CPPFLAGS'      : [],
761                 'CPPPATH'       : [],
762                 'LIBPATH'       : [],
763                 'LIBS'          : [],
764                 'LINKFLAGS'     : [],
765             }
766     
767             params = string.split(output)
768             append_next_arg_to=''       # for multi-word args
769             for arg in params:
770                 if append_next_arg_to:
771                     dict[append_next_arg_to].append(arg)
772                     append_next_arg_to = ''
773                 elif arg[0] != '-':
774                     dict['LIBS'].append(fs.File(arg))
775                 elif arg[:2] == '-L':
776                     if arg[2:]:
777                         dict['LIBPATH'].append(arg[2:])
778                     else:
779                         append_next_arg_to = 'LIBPATH'
780                 elif arg[:2] == '-l':
781                     if arg[2:]:
782                         dict['LIBS'].append(arg[2:])
783                     else:
784                         append_next_arg_to = 'LIBS'
785                 elif arg[:2] == '-I':
786                     if arg[2:]:
787                         dict['CPPPATH'].append(arg[2:])
788                     else:
789                         append_next_arg_to = 'CPPPATH'
790                 elif arg[:4] == '-Wa,':
791                     dict['ASFLAGS'].append(arg)
792                 elif arg[:4] == '-Wl,':
793                     dict['LINKFLAGS'].append(arg)
794                 elif arg[:4] == '-Wp,':
795                     dict['CPPFLAGS'].append(arg)
796                 elif arg == '-framework':
797                     dict['LINKFLAGS'].append(arg)
798                     append_next_arg_to='LINKFLAGS'
799                 elif arg == '-mno-cygwin':
800                     dict['CCFLAGS'].append(arg)
801                     dict['LINKFLAGS'].append(arg)
802                 elif arg == '-mwindows':
803                     dict['LINKFLAGS'].append(arg)
804                 elif arg == '-pthread':
805                     dict['CCFLAGS'].append(arg)
806                     dict['LINKFLAGS'].append(arg)
807                 else:
808                     dict['CCFLAGS'].append(arg)
809             apply(env.Append, (), dict)
810     
811         if function is None:
812             function = parse_conf
813         if type(command) is type([]):
814             command = string.join(command)
815         command = self.subst(command)
816         return function(self, os.popen(command).read())
817
818     def ParseDepends(self, filename, must_exist=None):
819         """
820         Parse a mkdep-style file for explicit dependencies.  This is
821         completely abusable, and should be unnecessary in the "normal"
822         case of proper SCons configuration, but it may help make
823         the transition from a Make hierarchy easier for some people
824         to swallow.  It can also be genuinely useful when using a tool
825         that can write a .d file, but for which writing a scanner would
826         be too complicated.
827         """
828         try:
829             fp = open(filename, 'r')
830         except IOError:
831             if must_exist:
832                 raise
833             return
834         for line in SCons.Util.LogicalLines(fp).readlines():
835             if line[0] == '#':
836                 continue
837             try:
838                 target, depends = string.split(line, ':', 1)
839             except:
840                 pass
841             else:
842                 self.Depends(string.split(target), string.split(depends))
843
844     def Platform(self, platform):
845         platform = self.subst(platform)
846         return SCons.Platform.Platform(platform)(self)
847
848     def Prepend(self, **kw):
849         """Prepend values to existing construction variables
850         in an Environment.
851         """
852         kw = copy_non_reserved_keywords(kw)
853         for key, val in kw.items():
854             # It would be easier on the eyes to write this using
855             # "continue" statements whenever we finish processing an item,
856             # but Python 1.5.2 apparently doesn't let you use "continue"
857             # within try:-except: blocks, so we have to nest our code.
858             try:
859                 orig = self._dict[key]
860             except KeyError:
861                 # No existing variable in the environment, so just set
862                 # it to the new value.
863                 self._dict[key] = val
864             else:
865                 try:
866                     # Most straightforward:  just try to add them
867                     # together.  This will work in most cases, when the
868                     # original and new values are of compatible types.
869                     self._dict[key] = val + orig
870                 except TypeError:
871                     try:
872                         # Try to update a dictionary value with another.
873                         # If orig isn't a dictionary, it won't have an
874                         # update() method; if val isn't a dictionary,
875                         # it won't have a keys() method.  Either way,
876                         # it's an AttributeError.
877                         orig.update(val)
878                     except AttributeError:
879                         try:
880                             # Check if the added value is a list.
881                             add_to_val = val.append
882                         except AttributeError:
883                             # The added value isn't a list, but the
884                             # original is (by process of elimination),
885                             # so insert the the new value in the original
886                             # (if there's one to insert).
887                             if val:
888                                 orig.insert(0, val)
889                         else:
890                             # The added value is a list, so append
891                             # the original to it (if there's a value
892                             # to append).
893                             if orig:
894                                 add_to_val(orig)
895                             self._dict[key] = val
896         self.scanner_map_delete(kw)
897
898     def PrependENVPath(self, name, newpath, envname = 'ENV', sep = os.pathsep):
899         """Prepend path elements to the path 'name' in the 'ENV'
900         dictionary for this environment.  Will only add any particular
901         path once, and will normpath and normcase all paths to help
902         assure this.  This can also handle the case where the env
903         variable is a list instead of a string.
904         """
905
906         orig = ''
907         if self._dict.has_key(envname) and self._dict[envname].has_key(name):
908             orig = self._dict[envname][name]
909
910         nv = SCons.Util.PrependPath(orig, newpath, sep)
911             
912         if not self._dict.has_key(envname):
913             self._dict[envname] = {}
914
915         self._dict[envname][name] = nv
916
917     def PrependUnique(self, **kw):
918         """Append values to existing construction variables
919         in an Environment, if they're not already there.
920         """
921         kw = copy_non_reserved_keywords(kw)
922         for key, val in kw.items():
923             if not self._dict.has_key(key):
924                 self._dict[key] = val
925             elif SCons.Util.is_Dict(self._dict[key]) and \
926                  SCons.Util.is_Dict(val):
927                 self._dict[key].update(val)
928             elif SCons.Util.is_List(val):
929                 dk = self._dict[key]
930                 if not SCons.Util.is_List(dk):
931                     dk = [dk]
932                 val = filter(lambda x, dk=dk: x not in dk, val)
933                 self._dict[key] = val + dk
934             else:
935                 dk = self._dict[key]
936                 if SCons.Util.is_List(dk):
937                     if not val in dk:
938                         self._dict[key] = val + dk
939                 else:
940                     self._dict[key] = val + dk
941         self.scanner_map_delete(kw)
942
943     def Replace(self, **kw):
944         """Replace existing construction variables in an Environment
945         with new construction variables and/or values.
946         """
947         try:
948             kwbd = our_deepcopy(kw['BUILDERS'])
949             del kw['BUILDERS']
950             self.__setitem__('BUILDERS', kwbd)
951         except KeyError:
952             pass
953         kw = copy_non_reserved_keywords(kw)
954         self._dict.update(our_deepcopy(kw))
955         self.scanner_map_delete(kw)
956
957     def ReplaceIxes(self, path, old_prefix, old_suffix, new_prefix, new_suffix):
958         """
959         Replace old_prefix with new_prefix and old_suffix with new_suffix.
960
961         env - Environment used to interpolate variables.
962         path - the path that will be modified.
963         old_prefix - construction variable for the old prefix.
964         old_suffix - construction variable for the old suffix.
965         new_prefix - construction variable for the new prefix.
966         new_suffix - construction variable for the new suffix.
967         """
968         old_prefix = self.subst('$'+old_prefix)
969         old_suffix = self.subst('$'+old_suffix)
970
971         new_prefix = self.subst('$'+new_prefix)
972         new_suffix = self.subst('$'+new_suffix)
973
974         dir,name = os.path.split(str(path))
975         if name[:len(old_prefix)] == old_prefix:
976             name = name[len(old_prefix):]
977         if name[-len(old_suffix):] == old_suffix:
978             name = name[:-len(old_suffix)]
979         return os.path.join(dir, new_prefix+name+new_suffix)
980
981     def SetDefault(self, **kw):
982         for k in kw.keys():
983             if self._dict.has_key(k):
984                 del kw[k]
985         apply(self.Replace, (), kw)
986
987     def Tool(self, tool, toolpath=[], **kw):
988         if SCons.Util.is_String(tool):
989             tool = self.subst(tool)
990             toolpath = map(self.subst, toolpath)
991             tool = apply(SCons.Tool.Tool, (tool, toolpath), kw)
992         tool(self)
993
994     def WhereIs(self, prog, path=None, pathext=None, reject=[]):
995         """Find prog in the path.  
996         """
997         if path is None:
998             try:
999                 path = self['ENV']['PATH']
1000             except KeyError:
1001                 pass
1002         elif SCons.Util.is_String(path):
1003             path = self.subst(path)
1004         if pathext is None:
1005             try:
1006                 pathext = self['ENV']['PATHEXT']
1007             except KeyError:
1008                 pass
1009         elif SCons.Util.is_String(pathext):
1010             pathext = self.subst(pathext)
1011         path = SCons.Util.WhereIs(prog, path, pathext, reject)
1012         if path: return path
1013         return None
1014
1015     #######################################################################
1016     # Public methods for doing real "SCons stuff" (manipulating
1017     # dependencies, setting attributes on targets, etc.).  These begin
1018     # with upper-case letters.  The essential characteristic of methods
1019     # in this section is that they all *should* have corresponding
1020     # same-named global functions.
1021     #######################################################################
1022
1023     def Action(self, *args, **kw):
1024         nargs = self.subst(args)
1025         nkw = self.subst_kw(kw)
1026         return apply(SCons.Action.Action, nargs, nkw)
1027
1028     def AddPreAction(self, files, action):
1029         nodes = self.arg2nodes(files, self.fs.Entry)
1030         action = SCons.Action.Action(action)
1031         for n in nodes:
1032             n.add_pre_action(action)
1033         return nodes
1034     
1035     def AddPostAction(self, files, action):
1036         nodes = self.arg2nodes(files, self.fs.Entry)
1037         action = SCons.Action.Action(action)
1038         for n in nodes:
1039             n.add_post_action(action)
1040         return nodes
1041
1042     def Alias(self, target, source=[], action=None, **kw):
1043         tlist = self.arg2nodes(target, self.ans.Alias)
1044         if not SCons.Util.is_List(source):
1045             source = [source]
1046         source = filter(None, source)
1047
1048         if not action:
1049             if not source:
1050                 # There are no source files and no action, so just
1051                 # return a target list of classic Alias Nodes, without
1052                 # any builder.  The externally visible effect is that
1053                 # this will make the wrapping Script.BuildTask class
1054                 # say that there's "Nothing to be done" for this Alias,
1055                 # instead of that it's "up to date."
1056                 return tlist
1057
1058             # No action, but there are sources.  Re-call all the target
1059             # builders to add the sources to each target.
1060             result = []
1061             for t in tlist:
1062                 bld = t.get_builder(AliasBuilder)
1063                 result.extend(bld(self, t, source))
1064             return result
1065
1066         action = SCons.Action.Action(action)
1067         nkw = self.subst_kw(kw)
1068         nkw['source_factory'] = self.fs.Entry
1069         nkw['multi'] = 1
1070         nkw['action'] = action
1071         bld = apply(SCons.Builder.Builder, (), nkw)
1072
1073         # Apply the Builder separately to each target so that the Aliases
1074         # stay separate.  If we did one "normal" Builder call with the
1075         # whole target list, then all of the target Aliases would be
1076         # associated under a single Executor.
1077         result = []
1078         for t in tlist:
1079             # Calling the convert() method will cause a new Executor to be
1080             # created from scratch, so we have to explicitly initialize
1081             # it with the target's existing sources, plus our new ones,
1082             # so nothing gets lost.
1083             b = t.get_builder()
1084             if b is None or b is AliasBuilder:
1085                 b = bld
1086             else:
1087                 nkw['action'] = b.action + action
1088                 b = apply(SCons.Builder.Builder, (), nkw)
1089             t.convert()
1090             result.extend(b(self, t, t.sources + source))
1091         return result
1092
1093     def AlwaysBuild(self, *targets):
1094         tlist = []
1095         for t in targets:
1096             tlist.extend(self.arg2nodes(t, self.fs.File))
1097         for t in tlist:
1098             t.set_always_build()
1099         return tlist
1100
1101     def BuildDir(self, build_dir, src_dir, duplicate=1):
1102         build_dir = self.arg2nodes(build_dir, self.fs.Dir)[0]
1103         src_dir = self.arg2nodes(src_dir, self.fs.Dir)[0]
1104         self.fs.BuildDir(build_dir, src_dir, duplicate)
1105
1106     def Builder(self, **kw):
1107         nkw = self.subst_kw(kw)
1108         return apply(SCons.Builder.Builder, [], nkw)
1109
1110     def CacheDir(self, path):
1111         self.fs.CacheDir(self.subst(path))
1112
1113     def Clean(self, targets, files):
1114         global CleanTargets
1115         tlist = self.arg2nodes(targets, self.fs.Entry)
1116         flist = self.arg2nodes(files, self.fs.Entry)
1117         for t in tlist:
1118             try:
1119                 CleanTargets[t].extend(flist)
1120             except KeyError:
1121                 CleanTargets[t] = flist
1122
1123     def Configure(self, *args, **kw):
1124         nargs = [self]
1125         if args:
1126             nargs = nargs + self.subst_list(args)[0]
1127         nkw = self.subst_kw(kw)
1128         nkw['called_from_env_method'] = 1
1129         try:
1130             nkw['custom_tests'] = self.subst_kw(nkw['custom_tests'])
1131         except KeyError:
1132             pass
1133         return apply(SCons.SConf.SConf, nargs, nkw)
1134
1135     def Command(self, target, source, action, **kw):
1136         """Builds the supplied target files from the supplied
1137         source files using the supplied action.  Action may
1138         be any type that the Builder constructor will accept
1139         for an action."""
1140         bld = SCons.Builder.Builder(action = action,
1141                                     source_factory = self.fs.Entry)
1142         return apply(bld, (self, target, source), kw)
1143
1144     def Depends(self, target, dependency):
1145         """Explicity specify that 'target's depend on 'dependency'."""
1146         tlist = self.arg2nodes(target, self.fs.Entry)
1147         dlist = self.arg2nodes(dependency, self.fs.Entry)
1148         for t in tlist:
1149             t.add_dependency(dlist)
1150         return tlist
1151
1152     def Dir(self, name, *args, **kw):
1153         """
1154         """
1155         return apply(self.fs.Dir, (self.subst(name),) + args, kw)
1156
1157     def Environment(self, **kw):
1158         return apply(SCons.Environment.Environment, [], self.subst_kw(kw))
1159
1160     def Execute(self, action, *args, **kw):
1161         """Directly execute an action through an Environment
1162         """
1163         action = apply(self.Action, (action,) + args, kw)
1164         return action([], [], self)
1165
1166     def File(self, name, *args, **kw):
1167         """
1168         """
1169         return apply(self.fs.File, (self.subst(name),) + args, kw)
1170
1171     def FindFile(self, file, dirs):
1172         file = self.subst(file)
1173         nodes = self.arg2nodes(dirs, self.fs.Dir)
1174         return SCons.Node.FS.find_file(file, nodes, self.fs.File)
1175
1176     def Flatten(self, sequence):
1177         return SCons.Util.flatten(sequence)
1178
1179     def GetBuildPath(self, files):
1180         result = map(str, self.arg2nodes(files, self.fs.Entry))
1181         if SCons.Util.is_List(files):
1182             return result
1183         else:
1184             return result[0]
1185
1186     def Ignore(self, target, dependency):
1187         """Ignore a dependency."""
1188         tlist = self.arg2nodes(target, self.fs.Entry)
1189         dlist = self.arg2nodes(dependency, self.fs.Entry)
1190         for t in tlist:
1191             t.add_ignore(dlist)
1192         return tlist
1193
1194     def Install(self, dir, source):
1195         """Install specified files in the given directory."""
1196         try:
1197             dnodes = self.arg2nodes(dir, self.fs.Dir)
1198         except TypeError:
1199             raise SCons.Errors.UserError, "Target `%s' of Install() is a file, but should be a directory.  Perhaps you have the Install() arguments backwards?" % str(dir)
1200         try:
1201             sources = self.arg2nodes(source, self.fs.File)
1202         except TypeError:
1203             if SCons.Util.is_List(source):
1204                 raise SCons.Errors.UserError, "Source `%s' of Install() contains one or more non-files.  Install() source must be one or more files." % repr(map(str, source))
1205             else:
1206                 raise SCons.Errors.UserError, "Source `%s' of Install() is not a file.  Install() source must be one or more files." % str(source)
1207         tgt = []
1208         for dnode in dnodes:
1209             for src in sources:
1210                 target = self.fs.File(src.name, dnode)
1211                 tgt.extend(InstallBuilder(self, target, src))
1212         return tgt
1213
1214     def InstallAs(self, target, source):
1215         """Install sources as targets."""
1216         sources = self.arg2nodes(source, self.fs.File)
1217         targets = self.arg2nodes(target, self.fs.File)
1218         result = []
1219         for src, tgt in map(lambda x, y: (x, y), sources, targets):
1220             result.extend(InstallBuilder(self, tgt, src))
1221         return result
1222
1223     def Literal(self, string):
1224         return SCons.Util.Literal(string)
1225
1226     def Local(self, *targets):
1227         ret = []
1228         for targ in targets:
1229             if isinstance(targ, SCons.Node.Node):
1230                 targ.set_local()
1231                 ret.append(targ)
1232             else:
1233                 for t in self.arg2nodes(targ, self.fs.Entry):
1234                    t.set_local()
1235                    ret.append(t)
1236         return ret
1237
1238     def Precious(self, *targets):
1239         tlist = []
1240         for t in targets:
1241             tlist.extend(self.arg2nodes(t, self.fs.Entry))
1242         for t in tlist:
1243             t.set_precious()
1244         return tlist
1245
1246     def Repository(self, *dirs, **kw):
1247         dirs = self.arg2nodes(list(dirs), self.fs.Dir)
1248         apply(self.fs.Repository, dirs, kw)
1249
1250     def Scanner(self, *args, **kw):
1251         nargs = []
1252         for arg in args:
1253             if SCons.Util.is_String(arg):
1254                 arg = self.subst(arg)
1255             nargs.append(arg)
1256         nkw = self.subst_kw(kw)
1257         return apply(SCons.Scanner.Scanner, nargs, nkw)
1258
1259     def SConsignFile(self, name=".sconsign", dbm_module=None):
1260         name = self.subst(name)
1261         if not os.path.isabs(name):
1262             name = os.path.join(str(self.fs.SConstruct_dir), name)
1263         SCons.SConsign.File(name, dbm_module)
1264
1265     def SideEffect(self, side_effect, target):
1266         """Tell scons that side_effects are built as side 
1267         effects of building targets."""
1268         side_effects = self.arg2nodes(side_effect, self.fs.Entry)
1269         targets = self.arg2nodes(target, self.fs.Entry)
1270
1271         for side_effect in side_effects:
1272             if side_effect.multiple_side_effect_has_builder():
1273                 raise SCons.Errors.UserError, "Multiple ways to build the same target were specified for: %s" % str(side_effect)
1274             side_effect.add_source(targets)
1275             side_effect.side_effect = 1
1276             self.Precious(side_effect)
1277             for target in targets:
1278                 target.side_effects.append(side_effect)
1279         return side_effects
1280
1281     def SourceCode(self, entry, builder):
1282         """Arrange for a source code builder for (part of) a tree."""
1283         entries = self.arg2nodes(entry, self.fs.Entry)
1284         for entry in entries:
1285             entry.set_src_builder(builder)
1286         return entries
1287
1288     def SourceSignatures(self, type):
1289         type = self.subst(type)
1290         if type == 'MD5':
1291             import SCons.Sig.MD5
1292             self._calc_module = SCons.Sig.MD5
1293         elif type == 'timestamp':
1294             import SCons.Sig.TimeStamp
1295             self._calc_module = SCons.Sig.TimeStamp
1296         else:
1297             raise UserError, "Unknown source signature type '%s'"%type
1298
1299     def Split(self, arg):
1300         """This function converts a string or list into a list of strings
1301         or Nodes.  This makes things easier for users by allowing files to
1302         be specified as a white-space separated list to be split.
1303         The input rules are:
1304             - A single string containing names separated by spaces. These will be
1305               split apart at the spaces.
1306             - A single Node instance
1307             - A list containing either strings or Node instances. Any strings
1308               in the list are not split at spaces.
1309         In all cases, the function returns a list of Nodes and strings."""
1310         if SCons.Util.is_List(arg):
1311             return map(self.subst, arg)
1312         elif SCons.Util.is_String(arg):
1313             return string.split(self.subst(arg))
1314         else:
1315             return [self.subst(arg)]
1316
1317     def TargetSignatures(self, type):
1318         type = self.subst(type)
1319         if type == 'build':
1320             self._build_signature = 1
1321         elif type == 'content':
1322             self._build_signature = 0
1323         else:
1324             raise SCons.Errors.UserError, "Unknown target signature type '%s'"%type
1325
1326     def Value(self, value):
1327         """
1328         """
1329         return SCons.Node.Python.Value(value)
1330
1331 # The entry point that will be used by the external world
1332 # to refer to a construction environment.  This allows the wrapper
1333 # interface to extend a construction environment for its own purposes
1334 # by subclassing SCons.Environment.Base and then assigning the
1335 # class to SCons.Environment.Environment.
1336
1337 Environment = Base
1338
1339 # An entry point for returning a proxy subclass instance that overrides
1340 # the subst*() methods so they don't actually perform construction
1341 # variable substitution.  This is specifically intended to be the shim
1342 # layer in between global function calls (which don't want construction
1343 # variable substitution) and the DefaultEnvironment() (which would
1344 # substitute variables if left to its own devices)."""
1345 #
1346 # We have to wrap this in a function that allows us to delay definition of
1347 # the class until it's necessary, so that when it subclasses Environment
1348 # it will pick up whatever Environment subclass the wrapper interface
1349 # might have assigned to SCons.Environment.Environment.
1350
1351 def NoSubstitutionProxy(subject):
1352     class _NoSubstitutionProxy(Environment):
1353         def __init__(self, subject):
1354             self.__dict__['__subject'] = subject
1355         def __getattr__(self, name):
1356             return getattr(self.__dict__['__subject'], name)
1357         def __setattr__(self, name, value):
1358             return setattr(self.__dict__['__subject'], name, value)
1359         def raw_to_mode(self, dict):
1360             try:
1361                 raw = dict['raw']
1362             except KeyError:
1363                 pass
1364             else:
1365                 del dict['raw']
1366                 dict['mode'] = raw
1367         def subst(self, string, *args, **kwargs):
1368             return string
1369         def subst_kw(self, kw, *args, **kwargs):
1370             return kw
1371         def subst_list(self, string, *args, **kwargs):
1372             nargs = (string, self,) + args
1373             nkw = kwargs.copy()
1374             nkw['gvars'] = {}
1375             self.raw_to_mode(nkw)
1376             return apply(SCons.Util.scons_subst_list, nargs, nkw)
1377         def subst_target_source(self, string, *args, **kwargs):
1378             nargs = (string, self,) + args
1379             nkw = kwargs.copy()
1380             nkw['gvars'] = {}
1381             self.raw_to_mode(nkw)
1382             return apply(SCons.Util.scons_subst, nargs, nkw)
1383     return _NoSubstitutionProxy(subject)