Commits

dirkbaechle committed 00a48b7

- continue debugging of subst calls

Comments (0)

Files changed (2)

src/engine/SCons/Environment.py

         lvars['__env__'] = self
         if executor:
             lvars.update(executor.get_lvars())
-        st = SCons.Subst.scons_subst(string, self, raw, target, source, gvars, lvars, conv)
+        if (not lvars):
+            st = SCons.Subst.scons_subst(string, self, raw, target, source, gvars, lvars, conv)
+        else:
+            st = SCons.Subst.scons_subst_lvars(string, self, raw, target, source, gvars, lvars, conv)
         if lv and key_cache:
             key_cache[string] = st
             

src/engine/SCons/Subst.py

 
     return result
 
+
+def scons_subst_lvars(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
+    """Expand a string or list containing construction variable
+    substitutions.
+
+    This is the work-horse function for substitutions in file names
+    and the like.  The companion scons_subst_list() function (below)
+    handles separating command lines into lists of arguments, so see
+    that function if that's what you're looking for.
+    """
+    if isinstance(strSubst, str) and strSubst.find('$') < 0:
+        return strSubst
+
+    class StringSubber(object):
+        """A class to construct the results of a scons_subst() call.
+
+        This binds a specific construction environment, mode, target and
+        source with two methods (substitute() and expand()) that handle
+        the expansion.
+        """
+        def __init__(self, env, mode, conv, gvars):
+            self.env = env
+            self.mode = mode
+            self.conv = conv
+            self.gvars = gvars
+
+        def expand(self, s, lvars):
+            """Expand a single "token" as necessary, returning an
+            appropriate string containing the expansion.
+
+            This handles expanding different types of things (strings,
+            lists, callables) appropriately.  It calls the wrapper
+            substitute() method to re-expand things as necessary, so that
+            the results of expansions of side-by-side strings still get
+            re-evaluated separately, not smushed together.
+            """
+            if is_String(s):
+                try:
+                    s0, s1 = s[:2]
+                except (IndexError, ValueError):
+                    return s
+                if s0 != '$':
+                    return s
+                if s1 == '$':
+                    return '$'
+                elif s1 in '()':
+                    return s
+                else:
+                    key = s[1:]
+                    if key[0] == '{' or key.find('.') >= 0:
+                        if key[0] == '{':
+                            key = key[1:-1]
+                        try:
+                            s = eval(key, self.gvars, lvars)
+                        except KeyboardInterrupt:
+                            raise
+                        except Exception, e:
+                            if e.__class__ in AllowableExceptions:
+                                return ''
+                            raise_exception(e, lvars['TARGETS'], s)
+                    else:
+                        if key in lvars:
+                            s = lvars[key]
+                        elif key in self.gvars:
+                            s = self.gvars[key]
+                        elif not NameError in AllowableExceptions:
+                            raise_exception(NameError(key), lvars['TARGETS'], s)
+                        else:
+                            return ''
+    
+                    # Before re-expanding the result, handle
+                    # recursive expansion by copying the local
+                    # variable dictionary and overwriting a null
+                    # string for the value of the variable name
+                    # we just expanded.
+                    #
+                    # This could potentially be optimized by only
+                    # copying lvars when s contains more expansions,
+                    # but lvars is usually supposed to be pretty
+                    # small, and deeply nested variable expansions
+                    # are probably more the exception than the norm,
+                    # so it should be tolerable for now.
+                    lv = lvars.copy()
+                    var = key.split('.')[0]
+                    lv[var] = ''
+                    return self.substitute(s, lv)
+            elif is_Sequence(s):
+                def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
+                    return conv(substitute(l, lvars))
+                return list(map(func, s))
+            elif callable(s):
+                try:
+                    s = s(target=lvars['TARGETS'],
+                         source=lvars['SOURCES'],
+                         env=self.env,
+                         for_signature=(self.mode != SUBST_CMD))
+                except TypeError:
+                    # This probably indicates that it's a callable
+                    # object that doesn't match our calling arguments
+                    # (like an Action).
+                    if self.mode == SUBST_RAW:
+                        return s
+                    s = self.conv(s)
+                return self.substitute(s, lvars)
+            elif s is None:
+                return ''
+            else:
+                return s
+
+        def nostr_expand(self, s, lvars):
+            """Expand a single "token" as necessary, returning an
+            appropriate string containing the expansion.
+
+            This handles expanding different types of things (strings,
+            lists, callables) appropriately.  It calls the wrapper
+            substitute() method to re-expand things as necessary, so that
+            the results of expansions of side-by-side strings still get
+            re-evaluated separately, not smushed together.
+            """
+            if is_String(s):
+                try:
+                    s0, s1 = s[:2]
+                except (IndexError, ValueError):
+                    return s
+                if s0 != '$':
+                    return s
+                if s1 == '$':
+                    return '$'
+                elif s1 in '()':
+                    return s
+                else:
+                    key = s[1:]
+                    if key[0] == '{' or key.find('.') >= 0:
+                        if key[0] == '{':
+                            key = key[1:-1]
+                        try:
+                            s = eval(key, self.gvars, lvars)
+                        except KeyboardInterrupt:
+                            raise
+                        except Exception, e:
+                            if e.__class__ in AllowableExceptions:
+                                return ''
+                            raise_exception(e, lvars['TARGETS'], s)
+                    else:
+                        if key in lvars:
+                            s = lvars[key]
+                        elif key in self.gvars:
+                            s = self.gvars[key]
+                        elif not NameError in AllowableExceptions:
+                            raise_exception(NameError(key), lvars['TARGETS'], s)
+                        else:
+                            return ''
+    
+                    # Before re-expanding the result, handle
+                    # recursive expansion by copying the local
+                    # variable dictionary and overwriting a null
+                    # string for the value of the variable name
+                    # we just expanded.
+                    #
+                    # This could potentially be optimized by only
+                    # copying lvars when s contains more expansions,
+                    # but lvars is usually supposed to be pretty
+                    # small, and deeply nested variable expansions
+                    # are probably more the exception than the norm,
+                    # so it should be tolerable for now.
+                    lv = lvars.copy()
+                    var = key.split('.')[0]
+                    lv[var] = ''
+                    return self.substitute(s, lv)
+            elif is_Sequence(s):
+                def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
+                    return conv(substitute(l, lvars))
+                return list(map(func, s))
+            elif callable(s):
+                try:
+                    s = s(target=lvars['TARGETS'],
+                         source=lvars['SOURCES'],
+                         env=self.env,
+                         for_signature=(self.mode != SUBST_CMD))
+                except TypeError:
+                    # This probably indicates that it's a callable
+                    # object that doesn't match our calling arguments
+                    # (like an Action).
+                    if self.mode == SUBST_RAW:
+                        return s
+                    s = self.conv(s)
+                return self.substitute(s, lvars)
+            elif s is None:
+                return ''
+            else:
+                return s
+
+
+        def substitute(self, args, lvars):
+            """Substitute expansions in an argument or list of arguments.
+
+            This serves as a wrapper for splitting up a string into
+            separate tokens.
+            """
+            #print lvars
+            if is_String(args) and not isinstance(args, CmdStringHolder):
+                args = str(args)        # In case it's a UserString.
+                try:
+                    def sub_match(match):
+                        return self.conv(self.expand(match.group(1), lvars))
+                    result = _dollar_exps.sub(sub_match, args)
+                except TypeError:
+                    # If the internal conversion routine doesn't return
+                    # strings (it could be overridden to return Nodes, for
+                    # example), then the 1.5.2 re module will throw this
+                    # exception.  Back off to a slower, general-purpose
+                    # algorithm that works for all data types.
+                    args = _separate_args.findall(args)
+                    result = []
+                    for a in args:
+                        result.append(self.conv(self.expand(a, lvars)))
+                    if len(result) == 1:
+                        result = result[0]
+                    else:
+                        result = ''.join(map(str, result))
+                return result
+            else:
+                return self.nostr_expand(args, lvars)
+
+    if conv is None:
+        conv = _strconv[mode]
+
+    # Doing this every time is a bit of a waste, since the Executor
+    # has typically already populated the OverrideEnvironment with
+    # $TARGET/$SOURCE variables.  We're keeping this (for now), though,
+    # because it supports existing behavior that allows us to call
+    # an Action directly with an arbitrary target+source pair, which
+    # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
+    # If we dropped that behavior (or found another way to cover it),
+    # we could get rid of this call completely and just rely on the
+    # Executor setting the variables.
+    if 'TARGET' not in lvars:
+        d = subst_dict(target, source)
+        if d:
+            lvars = lvars.copy()
+            lvars.update(d)
+
+    # We're (most likely) going to eval() things.  If Python doesn't
+    # find a __builtins__ value in the global dictionary used for eval(),
+    # it copies the current global values for you.  Avoid this by
+    # setting it explicitly and then deleting, so we don't pollute the
+    # construction environment Dictionary(ies) that are typically used
+    # for expansion.
+    gvars['__builtins__'] = __builtins__
+
+    ss = StringSubber(env, mode, conv, gvars)
+    result = ss.substitute(strSubst, lvars)
+
+    try:
+        del gvars['__builtins__']
+    except KeyError:
+        pass
+
+    if is_String(result):
+        # Remove $(-$) pairs and any stuff in between,
+        # if that's appropriate.
+        remove = _regex_remove[mode]
+        if remove:
+            result = remove.sub('', result)
+        if mode != SUBST_RAW:
+            # Compress strings of white space characters into
+            # a single space.
+            result = _space_sep.sub(' ', result).strip()
+    elif is_Sequence(result):
+        remove = _list_remove[mode]
+        if remove:
+            result = remove(result)
+
+    return result
+
+
 #Subst_List_Strings = {}
 
 def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):