3 Base class for construction Environments. These are
4 the primary objects used to communicate dependency and
5 construction information to the build engine.
7 Keyword arguments supplied when the construction Environment
8 is created are construction variables used to initialize the
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:
23 # The above copyright notice and this permission notice shall be included
24 # in all copies or substantial portions of the Software.
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.
35 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
44 from UserDict import UserDict
57 def installFunc(target, source, env):
58 """Install a source file into a target using the function specified
59 as the INSTALL construction variable."""
61 install = env['INSTALL']
63 raise SCons.Errors.UserError('Missing INSTALL construction variable.')
64 return install(target[0].path, source[0].path, env)
66 def installString(target, source, env):
67 return 'Install file: "%s" as "%s"' % (source[0], target[0])
69 installAction = SCons.Action.Action(installFunc, installString)
71 InstallBuilder = SCons.Builder.Builder(action=installAction)
74 """deepcopy lists and dictionaries, and just copy the reference
75 for everything else."""
76 if SCons.Util.is_Dict(x):
79 copy[key] = our_deepcopy(x[key])
80 elif SCons.Util.is_List(x):
81 copy = map(our_deepcopy, x)
86 def apply_tools(env, tools):
89 if SCons.Util.is_String(tool):
90 tool = SCons.Tool.Tool(tool)
94 """Wrapper class that associates an environment with a Builder at
96 def __init__(self, env, builder):
98 self.builder = builder
100 def __call__(self, *args, **kw):
101 return apply(self.builder, (self.env,) + args, kw)
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):
112 apply(self.builder.execute, (), kw)
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.
124 UserDict.__init__(self, dict)
126 def __setitem__(self, item, val):
127 UserDict.__setitem__(self, item, val)
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!
136 def setenvattr(self, item, val):
137 """Set the corresponding environment attribute for this Builder.
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."""
144 builder = val.builder
145 except AttributeError:
147 setattr(self.env, item, BuilderWrapper(self.env, builder))
149 def __delitem__(self, item):
150 UserDict.__delitem__(self, item)
151 delattr(self.env, item)
153 def update(self, dict):
154 for i, v in dict.items():
155 self.__setitem__(i, v)
158 """Base class for construction Environments. These are
159 the primary objects used to communicate dependency and
160 construction information to the build engine.
162 Keyword arguments supplied when the construction Environment
163 is created are construction variables used to initialize the
168 platform=SCons.Platform.Platform(),
172 self.fs = SCons.Node.FS.default_fs
173 self._dict = our_deepcopy(SCons.Defaults.ConstructionEnvironment)
175 self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
177 if SCons.Util.is_String(platform):
178 platform = SCons.Platform.Platform(platform)
179 self._dict['PLATFORM'] = str(platform)
182 # Apply the passed-in variables before calling the tools,
183 # because they may use some of them:
184 apply(self.Replace, (), kw)
186 # Update the environment with the customizable options
187 # before calling the tools, since they may use some of the options:
193 apply_tools(self, tools)
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)
199 # Update the environment with the customizable options
200 # after calling the tools, since they should override anything
205 def __cmp__(self, other):
206 return cmp(self._dict, other._dict)
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.
219 clone = copy.copy(self)
220 clone._dict = our_deepcopy(self._dict)
222 cbd = clone._dict['BUILDERS']
223 clone._dict['BUILDERS'] = BuilderDict(cbd, clone)
227 apply_tools(clone, tools)
229 # Apply passed-in variables after the new tools.
230 apply(clone.Replace, (), kw)
236 def Replace(self, **kw):
237 """Replace existing construction variables in an Environment
238 with new construction variables and/or values.
241 kwbd = our_deepcopy(kw['BUILDERS'])
243 self.__setitem__('BUILDERS', kwbd)
246 self._dict.update(our_deepcopy(kw))
248 def Append(self, **kw):
249 """Append values to existing construction variables
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])
266 self._dict[key] = self._dict[key] + kw[key]
268 def Prepend(self, **kw):
269 """Prepend values to existing construction variables
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])
286 self._dict[key] = kw[key] + self._dict[key]
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.
297 if self._dict.has_key(envname) and self._dict[envname].has_key(name):
298 orig = self._dict[envname][name]
300 nv = SCons.Util.PrependPath(orig, newpath, sep)
302 if not self._dict.has_key(envname):
303 self._dict[envname] = {}
305 self._dict[envname][name] = nv
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.
316 if self._dict.has_key(envname) and self._dict[envname].has_key(name):
317 orig = self._dict[envname][name]
319 nv = SCons.Util.AppendPath(orig, newpath, sep)
321 if not self._dict.has_key(envname):
322 self._dict[envname] = {}
324 self._dict[envname][name] = nv
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)
332 t.add_dependency(dlist)
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)
349 def AlwaysBuild(self, *targets):
352 tlist.extend(SCons.Node.arg2nodes(t, self.fs.File))
361 def Precious(self, *targets):
364 tlist.extend(SCons.Node.arg2nodes(t, self.fs.File))
373 def Dictionary(self, *args):
376 dlist = map(lambda x, s=self: s._dict[x], args)
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':
391 self._dict[key] = BuilderDict(kwbd, self)
392 self._dict[key].update(value)
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
398 def __getitem__(self, key):
399 return self._dict[key]
401 def __delitem__(self, key):
404 def has_key(self, key):
405 return self._dict.has_key(key)
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
412 bld = SCons.Builder.Builder(action=action,
413 source_factory=SCons.Node.FS.default_fs.Entry)
414 return bld(self, target, source)
416 def Install(self, dir, source):
417 """Install specified files in the given directory."""
419 dnodes = SCons.Node.arg2nodes(dir, self.fs.Dir)
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)
423 sources = SCons.Node.arg2nodes(source, self.fs.File)
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))
428 raise SCons.Errors.UserError, "Source `%s' of Install() is not a file. Install() source must be one or more files." % str(source)
432 target = SCons.Node.FS.default_fs.File(src.name, dnode)
433 tgt.append(InstallBuilder(self, target, src))
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)
443 for src, tgt in map(lambda x, y: (x, y), sources, targets):
444 ret.append(InstallBuilder(self, tgt, src))
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:
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)
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]
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
491 mode = SCons.Util.SUBST_RAW
493 mode = SCons.Util.SUBST_CMD
494 return SCons.Util.scons_subst(string, self, mode,
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."""
501 mode = SCons.Util.SUBST_RAW
503 mode = SCons.Util.SUBST_CMD
504 return SCons.Util.scons_subst_list(string, self, mode,
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.
512 if self._dict['SCANNERS']:
513 for scanner in self._dict['SCANNERS']:
514 if skey in scanner.skeys:
518 def get_builder(self, name):
519 """Fetch the builder with the specified name from the environment.
522 return self._dict['BUILDERS'][name]
526 def Detect(self, progs):
527 """Return the first available program in progs.
529 if not SCons.Util.is_List(progs):
532 path = self.WhereIs(prog)
536 def WhereIs(self, prog):
537 """Find prog in the path.
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)
550 def Override(self, overrides):
552 Produce a modified environment whose variables
553 are overriden by the overrides dictionaries.
555 overrides - a dictionary that will override
556 the variables of this environment.
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.
565 env = copy.copy(self)
566 env._dict = copy.copy(self._dict)
567 env._dict.update(overrides)
572 def get(self, key, default=None):
573 "Emulates the get() method of dictionaries."""
574 return self._dict.get(key, default)
577 "Emulates the items() method of dictionaries."""
578 return self._dict.items()
580 def FindIxes(self, paths, prefix, suffix):
582 Search a list of paths for something that matches the prefix and suffix.
584 paths - the list of paths or nodes.
585 prefix - construction variable for the prefix.
586 suffix - construction variable for the suffix.
589 suffix = self.subst('$%s'%suffix)
590 prefix = self.subst('$%s'%prefix)
593 dir,name = os.path.split(str(path))
594 if name[:len(prefix)] == prefix and name[-len(suffix):] == suffix:
597 def ReplaceIxes(self, path, old_prefix, old_suffix, new_prefix, new_suffix):
599 Replace old_prefix with new_prefix and old_suffix with new_suffix.
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.
608 old_prefix = self.subst('$%s'%old_prefix)
609 old_suffix = self.subst('$%s'%old_suffix)
611 new_prefix = self.subst('$%s'%new_prefix)
612 new_suffix = self.subst('$%s'%new_suffix)
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)