summaryrefslogtreecommitdiffstats
path: root/src/engine/SCons/Environment.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/SCons/Environment.py')
-rw-r--r--src/engine/SCons/Environment.py304
1 files changed, 229 insertions, 75 deletions
diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py
index 012c36c..2f4c34e 100644
--- a/src/engine/SCons/Environment.py
+++ b/src/engine/SCons/Environment.py
@@ -38,6 +38,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import copy
import os
import os.path
+import shlex
import string
from UserDict import UserDict
@@ -53,7 +54,6 @@ import SCons.Node.FS
import SCons.Node.Python
import SCons.Platform
import SCons.SConsign
-import SCons.Sig
import SCons.Subst
import SCons.Tool
import SCons.Util
@@ -125,8 +125,9 @@ def _set_BUILDERS(env, key, value):
for k in bd.keys():
del bd[k]
except KeyError:
- env._dict[key] = BuilderDict(kwbd, env)
- env._dict[key].update(value)
+ bd = BuilderDict(kwbd, env)
+ env._dict[key] = bd
+ bd.update(value)
def _del_SCANNERS(env, key):
del env._dict[key]
@@ -136,13 +137,72 @@ def _set_SCANNERS(env, key, value):
env._dict[key] = value
env.scanner_map_delete()
-class BuilderWrapper:
- """Wrapper class that associates an environment with a Builder at
- instantiation."""
- def __init__(self, env, builder):
- self.env = env
- self.builder = builder
+
+# The following is partly based on code in a comment added by Peter
+# Shannon at the following page (there called the "transplant" class):
+#
+# ASPN : Python Cookbook : Dynamically added methods to a class
+# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81732
+#
+# We had independently been using the idiom as BuilderWrapper, but
+# factoring out the common parts into this base class, and making
+# BuilderWrapper a subclass that overrides __call__() to enforce specific
+# Builder calling conventions, simplified some of our higher-layer code.
+
+class MethodWrapper:
+ """
+ A generic Wrapper class that associates a method (which can
+ actually be any callable) with an object. As part of creating this
+ MethodWrapper object an attribute with the specified (by default,
+ the name of the supplied method) is added to the underlying object.
+ When that new "method" is called, our __call__() method adds the
+ object as the first argument, simulating the Python behavior of
+ supplying "self" on method calls.
+
+ We hang on to the name by which the method was added to the underlying
+ base class so that we can provide a method to "clone" ourselves onto
+ a new underlying object being copied (without which we wouldn't need
+ to save that info).
+ """
+ def __init__(self, object, method, name=None):
+ if name is None:
+ name = method.__name__
+ self.object = object
+ self.method = method
+ self.name = name
+ setattr(self.object, name, self)
+
+ def __call__(self, *args, **kwargs):
+ nargs = (self.object,) + args
+ return apply(self.method, nargs, kwargs)
+
+ def clone(self, new_object):
+ """
+ Returns an object that re-binds the underlying "method" to
+ the specified new object.
+ """
+ return self.__class__(new_object, self.method, self.name)
+
+class BuilderWrapper(MethodWrapper):
+ """
+ A MethodWrapper subclass that that associates an environment with
+ a Builder.
+
+ This mainly exists to wrap the __call__() function so that all calls
+ to Builders can have their argument lists massaged in the same way
+ (treat a lone argument as the source, treat two arguments as target
+ then source, make sure both target and source are lists) without
+ having to have cut-and-paste code to do it.
+
+ As a bit of obsessive backwards compatibility, we also intercept
+ attempts to get or set the "env" or "builder" attributes, which were
+ the names we used before we put the common functionality into the
+ MethodWrapper base class. We'll keep this around for a while in case
+ people shipped Tool modules that reached into the wrapper (like the
+ Tool/qt.py module does, or did). There shouldn't be a lot attribute
+ fetching or setting on these, so a little extra work shouldn't hurt.
+ """
def __call__(self, target=None, source=_null, *args, **kw):
if source is _null:
source = target
@@ -151,7 +211,26 @@ class BuilderWrapper:
target = [target]
if not source is None and not SCons.Util.is_List(source):
source = [source]
- return apply(self.builder, (self.env, target, source) + args, kw)
+ return apply(MethodWrapper.__call__, (self, target, source) + args, kw)
+
+ def __repr__(self):
+ return '<BuilderWrapper %s>' % repr(self.name)
+
+ def __getattr__(self, name):
+ if name == 'env':
+ return self.object
+ elif name == 'builder':
+ return self.method
+ else:
+ return self.__dict__[name]
+
+ def __setattr__(self, name, value):
+ if name == 'env':
+ self.object = value
+ elif name == 'builder':
+ self.method = value
+ else:
+ self.__dict__[name] = value
# This allows a Builder to be executed directly
# through the Environment to which it's attached.
@@ -160,9 +239,9 @@ class BuilderWrapper:
# But we do have a unit test for this, and can't
# yet rule out that it would be useful in the
# future, so leave it for now.
- def execute(self, **kw):
- kw['env'] = self.env
- apply(self.builder.execute, (), kw)
+ #def execute(self, **kw):
+ # kw['env'] = self.env
+ # apply(self.builder.execute, (), kw)
class BuilderDict(UserDict):
"""This is a dictionary-like class used by an Environment to hold
@@ -181,26 +260,7 @@ class BuilderDict(UserDict):
def __setitem__(self, item, val):
UserDict.__setitem__(self, item, val)
- try:
- self.setenvattr(item, val)
- except AttributeError:
- # Have to catch this because sometimes __setitem__ gets
- # called out of __init__, when we don't have an env
- # attribute yet, nor do we want one!
- pass
-
- def setenvattr(self, item, val):
- """Set the corresponding environment attribute for this Builder.
-
- If the value is already a BuilderWrapper, we pull the builder
- out of it and make another one, so that making a copy of an
- existing BuilderDict is guaranteed separate wrappers for each
- Builder + Environment pair."""
- try:
- builder = val.builder
- except AttributeError:
- builder = val
- setattr(self.env, item, BuilderWrapper(self.env, builder))
+ BuilderWrapper(self.env, val, item)
def __delitem__(self, item):
UserDict.__delitem__(self, item)
@@ -248,6 +308,7 @@ class SubstitutionEnvironment:
self.lookup_list = SCons.Node.arg2nodes_lookups
self._dict = kw.copy()
self._init_special()
+ self.added_methods = []
#self._memo = {}
def _init_special(self):
@@ -450,7 +511,16 @@ class SubstitutionEnvironment:
environment with the specified name. If the name is omitted,
the default name is the name of the function itself.
"""
- SCons.Util.AddMethod(self, function, name)
+ method = MethodWrapper(self, function, name)
+ self.added_methods.append(method)
+
+ def RemoveMethod(self, function):
+ """
+ Removes the specified function's MethodWrapper from the
+ added_methods list, so we don't re-bind it when making a clone.
+ """
+ is_not_func = lambda dm, f=function: not dm.method is f
+ self.added_methods = filter(is_not_func, self.added_methods)
def Override(self, overrides):
"""
@@ -543,7 +613,7 @@ class SubstitutionEnvironment:
# -R dir (deprecated linker rpath)
# IBM compilers may also accept -qframeworkdir=foo
- params = string.split(arg)
+ params = shlex.split(arg)
append_next_arg_to = None # for multi-word args
for arg in params:
if append_next_arg_to:
@@ -695,6 +765,14 @@ def build_source(ss, result):
elif isinstance(s.disambiguate(), SCons.Node.FS.File):
result.append(s)
+def default_decide_source(dependency, target, prev_ni):
+ f = SCons.Defaults.DefaultEnvironment().decide_source
+ return f(dependency, target, prev_ni)
+
+def default_decide_target(dependency, target, prev_ni):
+ f = SCons.Defaults.DefaultEnvironment().decide_target
+ return f(dependency, target, prev_ni)
+
class Base(SubstitutionEnvironment):
"""Base class for "real" construction Environments. These are the
primary objects used to communicate dependency and construction
@@ -747,6 +825,16 @@ class Base(SubstitutionEnvironment):
self.lookup_list = SCons.Node.arg2nodes_lookups
self._dict = semi_deepcopy(SCons.Defaults.ConstructionEnvironment)
self._init_special()
+ self.added_methods = []
+
+ # We don't use AddMethod, or define these as methods in this
+ # class, because we *don't* want these functions to be bound
+ # methods. They need to operate independently so that the
+ # settings will work properly regardless of whether a given
+ # target ends up being built with a Base environment or an
+ # OverrideEnvironment or what have you.
+ self.decide_target = default_decide_target
+ self.decide_source = default_decide_source
self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
@@ -777,6 +865,8 @@ class Base(SubstitutionEnvironment):
# reserved variable name like TARGETS.
pass
+ SCons.Tool.Initializers(self)
+
if tools is None:
tools = self._dict.get('TOOLS', None)
if tools is None:
@@ -802,17 +892,6 @@ class Base(SubstitutionEnvironment):
except KeyError:
return None
- def get_calculator(self):
- try:
- module = self._calc_module
- c = apply(SCons.Sig.Calculator, (module,), CalculatorArgs)
- except AttributeError:
- # Note that we're calling get_calculator() here, so the
- # DefaultEnvironment() must have a _calc_module attribute
- # to avoid infinite recursion.
- c = SCons.Defaults.DefaultEnvironment().get_calculator()
- return c
-
def get_CacheDir(self):
try:
return self._CacheDir
@@ -901,13 +980,21 @@ class Base(SubstitutionEnvironment):
"""
self._dict.update(dict)
- def use_build_signature(self):
+ def get_src_sig_type(self):
+ try:
+ return self.src_sig_type
+ except AttributeError:
+ t = SCons.Defaults.DefaultEnvironment().src_sig_type
+ self.src_sig_type = t
+ return t
+
+ def get_tgt_sig_type(self):
try:
- return self._build_signature
+ return self.tgt_sig_type
except AttributeError:
- b = SCons.Defaults.DefaultEnvironment()._build_signature
- self._build_signature = b
- return b
+ t = SCons.Defaults.DefaultEnvironment().tgt_sig_type
+ self.tgt_sig_type = t
+ return t
#######################################################################
# Public methods for manipulating an Environment. These begin with
@@ -1040,28 +1127,84 @@ class Base(SubstitutionEnvironment):
"""
clone = copy.copy(self)
clone._dict = semi_deepcopy(self._dict)
+
try:
cbd = clone._dict['BUILDERS']
- clone._dict['BUILDERS'] = BuilderDict(cbd, clone)
except KeyError:
pass
+ else:
+ clone._dict['BUILDERS'] = BuilderDict(cbd, clone)
- clone._memo = {}
+ clone.added_methods = []
+ for mw in self.added_methods:
+ mw.clone(clone)
- apply_tools(clone, tools, toolpath)
+ clone._memo = {}
- # Apply passed-in variables after the new tools.
+ # Apply passed-in variables before the tools
+ # so the tools can use the new variables
kw = copy_non_reserved_keywords(kw)
new = {}
for key, value in kw.items():
new[key] = SCons.Subst.scons_subst_once(value, self, key)
apply(clone.Replace, (), new)
+
+ apply_tools(clone, tools, toolpath)
+
+ # apply them again in case the tools overwrote them
+ apply(clone.Replace, (), new)
+
if __debug__: logInstanceCreation(self, 'Environment.EnvironmentClone')
return clone
def Copy(self, *args, **kw):
return apply(self.Clone, args, kw)
+ def _changed_build(self, dependency, target, prev_ni):
+ if dependency.changed_state(target, prev_ni):
+ return 1
+ return self.decide_source(dependency, target, prev_ni)
+
+ def _changed_content(self, dependency, target, prev_ni):
+ return dependency.changed_content(target, prev_ni)
+
+ def _changed_source(self, dependency, target, prev_ni):
+ target_env = dependency.get_build_env()
+ type = target_env.get_tgt_sig_type()
+ if type == 'source':
+ return target_env.decide_source(dependency, target, prev_ni)
+ else:
+ return target_env.decide_target(dependency, target, prev_ni)
+
+ def _changed_timestamp_then_content(self, dependency, target, prev_ni):
+ return dependency.changed_timestamp_then_content(target, prev_ni)
+
+ def _changed_timestamp_newer(self, dependency, target, prev_ni):
+ return dependency.changed_timestamp_newer(target, prev_ni)
+
+ def _changed_timestamp_match(self, dependency, target, prev_ni):
+ return dependency.changed_timestamp_match(target, prev_ni)
+
+ def Decider(self, function):
+ if function in ('MD5', 'content'):
+ if not SCons.Util.md5:
+ raise UserError, "MD5 signatures are not available in this version of Python."
+ function = self._changed_content
+ elif function == 'MD5-timestamp':
+ function = self._changed_timestamp_then_content
+ elif function in ('timestamp-newer', 'make'):
+ function = self._changed_timestamp_newer
+ elif function == 'timestamp-match':
+ function = self._changed_timestamp_match
+ elif not callable(function):
+ raise UserError, "Unknown Decider value %s" % repr(function)
+
+ # We don't use AddMethod because we don't want to turn the
+ # function, which only expects three arguments, into a bound
+ # method, which would add self as an initial, fourth argument.
+ self.decide_target = function
+ self.decide_source = function
+
def Detect(self, progs):
"""Return the first available program in progs.
"""
@@ -1294,11 +1437,13 @@ class Base(SubstitutionEnvironment):
with new construction variables and/or values.
"""
try:
- kwbd = semi_deepcopy(kw['BUILDERS'])
- del kw['BUILDERS']
- self.__setitem__('BUILDERS', kwbd)
+ kwbd = kw['BUILDERS']
except KeyError:
pass
+ else:
+ kwbd = semi_deepcopy(kwbd)
+ del kw['BUILDERS']
+ self.__setitem__('BUILDERS', kwbd)
kw = copy_non_reserved_keywords(kw)
self._update(semi_deepcopy(kw))
self.scanner_map_delete(kw)
@@ -1659,21 +1804,15 @@ class Base(SubstitutionEnvironment):
def SourceSignatures(self, type):
type = self.subst(type)
+ self.src_sig_type = type
if type == 'MD5':
- try:
- import SCons.Sig.MD5
- except ImportError:
- msg = "No MD5 module available, using time stamps"
- SCons.Warnings.warn(SCons.Warnings.NoMD5ModuleWarning, msg)
- import SCons.Sig.TimeStamp
- self._calc_module = SCons.Sig.TimeStamp
- else:
- self._calc_module = SCons.Sig.MD5
+ if not SCons.Util.md5:
+ raise UserError, "MD5 signatures are not available in this version of Python."
+ self.decide_source = self._changed_content
elif type == 'timestamp':
- import SCons.Sig.TimeStamp
- self._calc_module = SCons.Sig.TimeStamp
+ self.decide_source = self._changed_timestamp_newer
else:
- raise UserError, "Unknown source signature type '%s'"%type
+ raise UserError, "Unknown source signature type '%s'" % type
def Split(self, arg):
"""This function converts a string or list into a list of strings
@@ -1695,12 +1834,19 @@ class Base(SubstitutionEnvironment):
def TargetSignatures(self, type):
type = self.subst(type)
- if type == 'build':
- self._build_signature = 1
- elif type == 'content':
- self._build_signature = 0
+ self.tgt_sig_type = type
+ if type in ('MD5', 'content'):
+ if not SCons.Util.md5:
+ raise UserError, "MD5 signatures are not available in this version of Python."
+ self.decide_target = self._changed_content
+ elif type == 'timestamp':
+ self.decide_target = self._changed_timestamp_newer
+ elif type == 'build':
+ self.decide_target = self._changed_build
+ elif type == 'source':
+ self.decide_target = self._changed_source
else:
- raise SCons.Errors.UserError, "Unknown target signature type '%s'"%type
+ raise UserError, "Unknown target signature type '%s'"%type
def Value(self, value, built_value=None):
"""
@@ -1740,6 +1886,14 @@ class Base(SubstitutionEnvironment):
# remove duplicates
return list(set(sources))
+ def FindInstalledFiles(self):
+ """ returns the list of all targets of the Install and InstallAs Builder.
+ """
+ from SCons.Tool import install
+ if install._UNIQUE_INSTALLED_FILES is None:
+ install._UNIQUE_INSTALLED_FILES = SCons.Util.uniquer_hashables(install._INSTALLED_FILES)
+ return install._UNIQUE_INSTALLED_FILES
+
class OverrideEnvironment(Base):
"""A proxy that overrides variables in a wrapped construction
environment by returning values from an overrides dictionary in