d0a22b5672129ba983a6273b77e44cc960fcea54
[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 import re
43 import shutil
44 from UserDict import UserDict
45
46 import SCons.Action
47 import SCons.Builder
48 import SCons.Defaults
49 import SCons.Errors
50 import SCons.Node
51 import SCons.Node.FS
52 import SCons.Platform
53 import SCons.Tool
54 import SCons.Util
55 import SCons.Warnings
56
57 def installFunc(target, source, env):
58     """Install a source file into a target using the function specified
59     as the INSTALL construction variable."""
60     try:
61         install = env['INSTALL']
62     except KeyError:
63         raise SCons.Errors.UserError('Missing INSTALL construction variable.')
64     return install(target[0].path, source[0].path, env)
65
66 def installString(target, source, env):
67     return 'Install file: "%s" as "%s"' % (source[0], target[0])
68
69 installAction = SCons.Action.Action(installFunc, installString)
70
71 InstallBuilder = SCons.Builder.Builder(action=installAction)
72
73 def our_deepcopy(x):
74    """deepcopy lists and dictionaries, and just copy the reference
75    for everything else."""
76    if SCons.Util.is_Dict(x):
77        copy = {}
78        for key in x.keys():
79            copy[key] = our_deepcopy(x[key])
80    elif SCons.Util.is_List(x):
81        copy = map(our_deepcopy, x)
82    else:
83        copy = x
84    return copy
85
86 def apply_tools(env, tools):
87     if tools:
88         for tool in tools:
89             if SCons.Util.is_String(tool):
90                 tool = SCons.Tool.Tool(tool)
91             tool(env)
92
93 class BuilderWrapper:
94     """Wrapper class that associates an environment with a Builder at
95     instantiation."""
96     def __init__(self, env, builder):
97         self.env = env
98         self.builder = builder
99
100     def __call__(self, *args, **kw):
101         return apply(self.builder, (self.env,) + args, kw)
102
103     # This allows a Builder to be executed directly
104     # through the Environment to which it's attached.
105     # In practice, we shouldn't need this, because
106     # builders actually get executed through a Node.
107     # But we do have a unit test for this, and can't
108     # yet rule out that it would be useful in the
109     # future, so leave it for now.
110     def execute(self, **kw):
111         kw['env'] = self.env
112         apply(self.builder.execute, (), kw)
113
114 class BuilderDict(UserDict):
115     """This is a dictionary-like class used by an Environment to hold
116     the Builders.  We need to do this because every time someone changes
117     the Builders in the Environment's BUILDERS dictionary, we must
118     update the Environment's attributes."""
119     def __init__(self, dict, env):
120         # Set self.env before calling the superclass initialization,
121         # because it will end up calling our other methods, which will
122         # need to point the values in this dictionary to self.env.
123         self.env = env
124         UserDict.__init__(self, dict)
125
126     def __setitem__(self, item, val):
127         UserDict.__setitem__(self, item, val)
128         try:
129             self.setenvattr(item, val)
130         except AttributeError:
131             # Have to catch this because sometimes __setitem__ gets
132             # called out of __init__, when we don't have an env
133             # attribute yet, nor do we want one!
134             pass
135
136     def setenvattr(self, item, val):
137         """Set the corresponding environment attribute for this Builder.
138
139         If the value is already a BuilderWrapper, we pull the builder
140         out of it and make another one, so that making a copy of an
141         existing BuilderDict is guaranteed separate wrappers for each
142         Builder + Environment pair."""
143         try:
144             builder = val.builder
145         except AttributeError:
146             builder = val
147         setattr(self.env, item, BuilderWrapper(self.env, builder))
148
149     def __delitem__(self, item):
150         UserDict.__delitem__(self, item)
151         delattr(self.env, item)
152
153     def update(self, dict):
154         for i, v in dict.items():
155             self.__setitem__(i, v)
156
157 class Environment:
158     """Base class for construction Environments.  These are
159     the primary objects used to communicate dependency and
160     construction information to the build engine.
161
162     Keyword arguments supplied when the construction Environment
163     is created are construction variables used to initialize the
164     Environment.
165     """
166
167     def __init__(self,
168                  platform=SCons.Platform.Platform(),
169                  tools=None,
170                  options=None,
171                  **kw):
172         self.fs = SCons.Node.FS.default_fs
173         self._dict = our_deepcopy(SCons.Defaults.ConstructionEnvironment)
174
175         self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
176
177         if SCons.Util.is_String(platform):
178             platform = SCons.Platform.Platform(platform)
179         self._dict['PLATFORM'] = str(platform)
180         platform(self)
181
182         # Apply the passed-in variables before calling the tools,
183         # because they may use some of them:
184         apply(self.Replace, (), kw)
185         
186         # Update the environment with the customizable options
187         # before calling the tools, since they may use some of the options: 
188         if options:
189             options.Update(self)
190
191         if tools is None:
192             tools = ['default']
193         apply_tools(self, tools)
194
195         # Reapply the passed in variables after calling the tools,
196         # since they should overide anything set by the tools:
197         apply(self.Replace, (), kw)
198
199         # Update the environment with the customizable options
200         # after calling the tools, since they should override anything
201         # set by the tools:
202         if options:
203             options.Update(self)
204
205     def __cmp__(self, other):
206         return cmp(self._dict, other._dict)
207
208     def Builders(self):
209         pass    # XXX
210
211     def Copy(self, tools=None, **kw):
212         """Return a copy of a construction Environment.  The
213         copy is like a Python "deep copy"--that is, independent
214         copies are made recursively of each objects--except that
215         a reference is copied when an object is not deep-copyable
216         (like a function).  There are no references to any mutable
217         objects in the original Environment.
218         """
219         clone = copy.copy(self)
220         clone._dict = our_deepcopy(self._dict)
221         try:
222             cbd = clone._dict['BUILDERS']
223             clone._dict['BUILDERS'] = BuilderDict(cbd, clone)
224         except KeyError:
225             pass
226         
227         apply_tools(clone, tools)
228
229         # Apply passed-in variables after the new tools.
230         apply(clone.Replace, (), kw)
231         return clone
232
233     def Scanners(self):
234         pass    # XXX
235
236     def Replace(self, **kw):
237         """Replace existing construction variables in an Environment
238         with new construction variables and/or values.
239         """
240         try:
241             kwbd = our_deepcopy(kw['BUILDERS'])
242             del kw['BUILDERS']
243             self.__setitem__('BUILDERS', kwbd)
244         except KeyError:
245             pass
246         self._dict.update(our_deepcopy(kw))
247
248     def Append(self, **kw):
249         """Append values to existing construction variables
250         in an Environment.
251         """
252         kw = our_deepcopy(kw)
253         for key in kw.keys():
254             if not self._dict.has_key(key):
255                 self._dict[key] = kw[key]
256             elif SCons.Util.is_List(self._dict[key]) and not \
257                  SCons.Util.is_List(kw[key]):
258                 self._dict[key] = self._dict[key] + [ kw[key] ]
259             elif SCons.Util.is_List(kw[key]) and not \
260                  SCons.Util.is_List(self._dict[key]):
261                 self._dict[key] = [ self._dict[key] ] + kw[key]
262             elif SCons.Util.is_Dict(self._dict[key]) and \
263                  SCons.Util.is_Dict(kw[key]):
264                 self._dict[key].update(kw[key])
265             else:
266                 self._dict[key] = self._dict[key] + kw[key]
267
268     def Prepend(self, **kw):
269         """Prepend values to existing construction variables
270         in an Environment.
271         """
272         kw = our_deepcopy(kw)
273         for key in kw.keys():
274             if not self._dict.has_key(key):
275                 self._dict[key] = kw[key]
276             elif SCons.Util.is_List(self._dict[key]) and not \
277                  SCons.Util.is_List(kw[key]):
278                 self._dict[key] = [ kw[key] ] + self._dict[key]
279             elif SCons.Util.is_List(kw[key]) and not \
280                  SCons.Util.is_List(self._dict[key]):
281                 self._dict[key] = kw[key] + [ self._dict[key] ]
282             elif SCons.Util.is_Dict(self._dict[key]) and \
283                  SCons.Util.is_Dict(kw[key]):
284                 self._dict[key].update(kw[key])
285             else:
286                 self._dict[key] = kw[key] + self._dict[key]
287
288     def PrependENVPath(self, name, newpath, envname = 'ENV', sep = os.pathsep):
289         """Prepend path elements to the path 'name' in the 'ENV'
290         dictionary for this environment.  Will only add any particular
291         path once, and will normpath and normcase all paths to help
292         assure this.  This can also handle the case where the env
293         variable is a list instead of a string.
294         """
295
296         orig = ''
297         if self._dict.has_key(envname) and self._dict[envname].has_key(name):
298             orig = self._dict[envname][name]
299
300         nv = SCons.Util.PrependPath(orig, newpath, sep)
301             
302         if not self._dict.has_key(envname):
303             self._dict[envname] = {}
304
305         self._dict[envname][name] = nv
306
307     def AppendENVPath(self, name, newpath, envname = 'ENV', sep = os.pathsep):
308         """Append path elements to the path 'name' in the 'ENV'
309         dictionary for this environment.  Will only add any particular
310         path once, and will normpath and normcase all paths to help
311         assure this.  This can also handle the case where the env
312         variable is a list instead of a string.
313         """
314
315         orig = ''
316         if self._dict.has_key(envname) and self._dict[envname].has_key(name):
317             orig = self._dict[envname][name]
318
319         nv = SCons.Util.AppendPath(orig, newpath, sep)
320             
321         if not self._dict.has_key(envname):
322             self._dict[envname] = {}
323
324         self._dict[envname][name] = nv
325
326
327     def Depends(self, target, dependency):
328         """Explicity specify that 'target's depend on 'dependency'."""
329         tlist = SCons.Node.arg2nodes(target, self.fs.File)
330         dlist = SCons.Node.arg2nodes(dependency, self.fs.File)
331         for t in tlist:
332             t.add_dependency(dlist)
333
334         if len(tlist) == 1:
335             tlist = tlist[0]
336         return tlist
337
338     def Ignore(self, target, dependency):
339         """Ignore a dependency."""
340         tlist = SCons.Node.arg2nodes(target, self.fs.File)
341         dlist = SCons.Node.arg2nodes(dependency, self.fs.File)
342         for t in tlist:
343             t.add_ignore(dlist)
344
345         if len(tlist) == 1:
346             tlist = tlist[0]
347         return tlist
348
349     def AlwaysBuild(self, *targets):
350         tlist = []
351         for t in targets:
352             tlist.extend(SCons.Node.arg2nodes(t, self.fs.File))
353
354         for t in tlist:
355             t.set_always_build()
356
357         if len(tlist) == 1:
358             tlist = tlist[0]
359         return tlist
360
361     def Precious(self, *targets):
362         tlist = []
363         for t in targets:
364             tlist.extend(SCons.Node.arg2nodes(t, self.fs.File))
365
366         for t in tlist:
367             t.set_precious()
368
369         if len(tlist) == 1:
370             tlist = tlist[0]
371         return tlist
372
373     def Dictionary(self, *args):
374         if not args:
375             return self._dict
376         dlist = map(lambda x, s=self: s._dict[x], args)
377         if len(dlist) == 1:
378             dlist = dlist[0]
379         return dlist
380
381     def __setitem__(self, key, value):
382         if key in ['TARGET', 'TARGETS', 'SOURCE', 'SOURCES']:
383             SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning,
384                                 "Ignoring attempt to set reserved variable `%s'" % key)
385         elif key == 'BUILDERS':
386             try:
387                 bd = self._dict[key]
388                 for k in bd.keys():
389                     del bd[k]
390             except KeyError:
391                 self._dict[key] = BuilderDict(kwbd, self)
392             self._dict[key].update(value)
393         else:
394             if not SCons.Util.is_valid_construction_var(key):
395                 raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key
396             self._dict[key] = value
397
398     def __getitem__(self, key):
399         return self._dict[key]
400
401     def __delitem__(self, key):
402         del self._dict[key]
403
404     def has_key(self, key):
405         return self._dict.has_key(key)
406
407     def Command(self, target, source, action):
408         """Builds the supplied target files from the supplied
409         source files using the supplied action.  Action may
410         be any type that the Builder constructor will accept
411         for an action."""
412         bld = SCons.Builder.Builder(action=action,
413                                     source_factory=SCons.Node.FS.default_fs.Entry)
414         return bld(self, target, source)
415
416     def Install(self, dir, source):
417         """Install specified files in the given directory."""
418         try:
419             dnodes = SCons.Node.arg2nodes(dir, self.fs.Dir)
420         except TypeError:
421             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)
422         try:
423             sources = SCons.Node.arg2nodes(source, self.fs.File)
424         except TypeError:
425             if SCons.Util.is_List(source):
426                 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))
427             else:
428                 raise SCons.Errors.UserError, "Source `%s' of Install() is not a file.  Install() source must be one or more files." % str(source)
429         tgt = []
430         for dnode in dnodes:
431             for src in sources:
432                 target = SCons.Node.FS.default_fs.File(src.name, dnode)
433                 tgt.append(InstallBuilder(self, target, src))
434         if len(tgt) == 1:
435             tgt = tgt[0]
436         return tgt
437
438     def InstallAs(self, target, source):
439         """Install sources as targets."""
440         sources = SCons.Node.arg2nodes(source, self.fs.File)
441         targets = SCons.Node.arg2nodes(target, self.fs.File)
442         ret = []
443         for src, tgt in map(lambda x, y: (x, y), sources, targets):
444             ret.append(InstallBuilder(self, tgt, src))
445         if len(ret) == 1:
446             ret = ret[0]
447         return ret
448
449     def SourceCode(self, entry, builder):
450         """Arrange for a source code builder for (part of) a tree."""
451         entries = SCons.Node.arg2nodes(entry, self.fs.Entry)
452         for entry in entries:
453             entry.set_src_builder(builder)
454         if len(entries) == 1:
455             return entries[0]
456         return entries
457
458     def SideEffect(self, side_effect, target):
459         """Tell scons that side_effects are built as side 
460         effects of building targets."""
461         side_effects = SCons.Node.arg2nodes(side_effect, self.fs.File)
462         targets = SCons.Node.arg2nodes(target, self.fs.File)
463
464         for side_effect in side_effects:
465             # A builder of 1 means the node is supposed to appear
466             # buildable without actually having a builder, so we allow
467             # it to be a side effect as well.
468             if side_effect.has_builder() and side_effect.builder != 1:
469                 raise SCons.Errors.UserError, "Multiple ways to build the same target were specified for: %s" % str(side_effect)
470             side_effect.add_source(targets)
471             side_effect.side_effect = 1
472             self.Precious(side_effect)
473             for target in targets:
474                 target.side_effects.append(side_effect)
475         if len(side_effects) == 1:
476             return side_effects[0]
477         else:
478             return side_effects
479
480     def subst(self, string, raw=0, target=None, source=None):
481         """Recursively interpolates construction variables from the
482         Environment into the specified string, returning the expanded
483         result.  Construction variables are specified by a $ prefix
484         in the string and begin with an initial underscore or
485         alphabetic character followed by any number of underscores
486         or alphanumeric characters.  The construction variable names
487         may be surrounded by curly braces to separate the name from
488         trailing characters.
489         """
490         if raw:
491             mode = SCons.Util.SUBST_RAW
492         else:
493             mode = SCons.Util.SUBST_CMD
494         return SCons.Util.scons_subst(string, self, mode,
495                                       target, source)
496     
497     def subst_list(self, string, raw=0, target=None, source=None):
498         """Calls through to SCons.Util.scons_subst_list().  See
499         the documentation for that function."""
500         if raw:
501             mode = SCons.Util.SUBST_RAW
502         else:
503             mode = SCons.Util.SUBST_CMD
504         return SCons.Util.scons_subst_list(string, self, mode,
505                                            target, source)
506
507     def get_scanner(self, skey):
508         """Find the appropriate scanner given a key (usually a file suffix).
509         Does a linear search. Could be sped up by creating a dictionary if
510         this proves too slow.
511         """
512         if self._dict['SCANNERS']:
513             for scanner in self._dict['SCANNERS']:
514                 if skey in scanner.skeys:
515                     return scanner
516         return None
517
518     def get_builder(self, name):
519         """Fetch the builder with the specified name from the environment.
520         """
521         try:
522             return self._dict['BUILDERS'][name]
523         except KeyError:
524             return None
525
526     def Detect(self, progs):
527         """Return the first available program in progs.
528         """
529         if not SCons.Util.is_List(progs):
530             progs = [ progs ]
531         for prog in progs:
532             path = self.WhereIs(prog)
533             if path: return prog
534         return None
535
536     def WhereIs(self, prog):
537         """Find prog in the path.  
538         """
539         path = None
540         pathext = None
541         if self.has_key('ENV'):
542             if self['ENV'].has_key('PATH'):
543                 path = self['ENV']['PATH']
544             if self['ENV'].has_key('PATHEXT'):
545                 pathext = self['ENV']['PATHEXT']
546         path = SCons.Util.WhereIs(prog, path, pathext)
547         if path: return path
548         return None
549
550     def Override(self, overrides):
551         """
552         Produce a modified environment whose variables
553         are overriden by the overrides dictionaries.
554
555         overrides - a dictionary that will override
556         the variables of this environment.
557
558         This function is much more efficient than Copy()
559         or creating a new Environment because it doesn't do
560         a deep copy of the dictionary, and doesn't do a copy
561         at all if there are no overrides.
562         """
563
564         if overrides:
565             env = copy.copy(self)
566             env._dict = copy.copy(self._dict)
567             env._dict.update(overrides)
568             return env
569         else:
570             return self
571
572     def get(self, key, default=None):
573         "Emulates the get() method of dictionaries."""
574         return self._dict.get(key, default)
575
576     def items(self):
577         "Emulates the items() method of dictionaries."""
578         return self._dict.items()
579
580     def FindIxes(self, paths, prefix, suffix):
581         """
582         Search a list of paths for something that matches the prefix and suffix.
583
584         paths - the list of paths or nodes.
585         prefix - construction variable for the prefix.
586         suffix - construction variable for the suffix.
587         """
588
589         suffix = self.subst('$%s'%suffix)
590         prefix = self.subst('$%s'%prefix)
591
592         for path in paths:
593             dir,name = os.path.split(str(path))
594             if name[:len(prefix)] == prefix and name[-len(suffix):] == suffix: 
595                 return path
596
597     def ReplaceIxes(self, path, old_prefix, old_suffix, new_prefix, new_suffix):
598         """
599         Replace old_prefix with new_prefix and old_suffix with new_suffix.
600
601         env - Environment used to interpolate variables.
602         path - the path that will be modified.
603         old_prefix - construction variable for the old prefix.
604         old_suffix - construction variable for the old suffix.
605         new_prefix - construction variable for the new prefix.
606         new_suffix - construction variable for the new suffix.
607         """
608         old_prefix = self.subst('$%s'%old_prefix)
609         old_suffix = self.subst('$%s'%old_suffix)
610
611         new_prefix = self.subst('$%s'%new_prefix)
612         new_suffix = self.subst('$%s'%new_suffix)
613
614         dir,name = os.path.split(str(path))
615         if name[:len(old_prefix)] == old_prefix:
616             name = name[len(old_prefix):]
617         if name[-len(old_suffix):] == old_suffix:
618             name = name[:-len(old_suffix)]
619         return os.path.join(dir, new_prefix+name+new_suffix)