b100473727e873f74ca87c20e86cfd871daa564e
[scons.git] / src / engine / SCons / Subst.py
1 """SCons.Subst
2
3 SCons string substitution.
4
5 """
6
7 #
8 # __COPYRIGHT__
9 #
10 # Permission is hereby granted, free of charge, to any person obtaining
11 # a copy of this software and associated documentation files (the
12 # "Software"), to deal in the Software without restriction, including
13 # without limitation the rights to use, copy, modify, merge, publish,
14 # distribute, sublicense, and/or sell copies of the Software, and to
15 # permit persons to whom the Software is furnished to do so, subject to
16 # the following conditions:
17 #
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
20 #
21 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
22 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
23 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 #
29
30 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
31
32 import __builtin__
33 import re
34 import string
35 import types
36 import UserList
37
38 import SCons.Errors
39
40 from SCons.Util import is_String, is_List, is_Tuple
41
42 # Indexed by the SUBST_* constants below.
43 _strconv = [SCons.Util.to_String,
44             SCons.Util.to_String,
45             SCons.Util.to_String_for_signature]
46
47 class Literal:
48     """A wrapper for a string.  If you use this object wrapped
49     around a string, then it will be interpreted as literal.
50     When passed to the command interpreter, all special
51     characters will be escaped."""
52     def __init__(self, lstr):
53         self.lstr = lstr
54
55     def __str__(self):
56         return self.lstr
57
58     def escape(self, escape_func):
59         return escape_func(self.lstr)
60
61     def for_signature(self):
62         return self.lstr
63
64     def is_literal(self):
65         return 1
66
67 class SpecialAttrWrapper:
68     """This is a wrapper for what we call a 'Node special attribute.'
69     This is any of the attributes of a Node that we can reference from
70     Environment variable substitution, such as $TARGET.abspath or
71     $SOURCES[1].filebase.  We implement the same methods as Literal
72     so we can handle special characters, plus a for_signature method,
73     such that we can return some canonical string during signature
74     calculation to avoid unnecessary rebuilds."""
75
76     def __init__(self, lstr, for_signature=None):
77         """The for_signature parameter, if supplied, will be the
78         canonical string we return from for_signature().  Else
79         we will simply return lstr."""
80         self.lstr = lstr
81         if for_signature:
82             self.forsig = for_signature
83         else:
84             self.forsig = lstr
85
86     def __str__(self):
87         return self.lstr
88
89     def escape(self, escape_func):
90         return escape_func(self.lstr)
91
92     def for_signature(self):
93         return self.forsig
94
95     def is_literal(self):
96         return 1
97
98 def quote_spaces(arg):
99     """Generic function for putting double quotes around any string that
100     has white space in it."""
101     if ' ' in arg or '\t' in arg:
102         return '"%s"' % arg
103     else:
104         return str(arg)
105
106 class CmdStringHolder(SCons.Util.UserString):
107     """This is a special class used to hold strings generated by
108     scons_subst() and scons_subst_list().  It defines a special method
109     escape().  When passed a function with an escape algorithm for a
110     particular platform, it will return the contained string with the
111     proper escape sequences inserted.
112
113     This should really be a subclass of UserString, but that module
114     doesn't exist in Python 1.5.2."""
115     def __init__(self, cmd, literal=None):
116         SCons.Util.UserString.__init__(self, cmd)
117         self.literal = literal
118
119     def is_literal(self):
120         return self.literal
121
122     def escape(self, escape_func, quote_func=quote_spaces):
123         """Escape the string with the supplied function.  The
124         function is expected to take an arbitrary string, then
125         return it with all special characters escaped and ready
126         for passing to the command interpreter.
127
128         After calling this function, the next call to str() will
129         return the escaped string.
130         """
131
132         if self.is_literal():
133             return escape_func(self.data)
134         elif ' ' in self.data or '\t' in self.data:
135             return quote_func(self.data)
136         else:
137             return self.data
138
139 def escape_list(list, escape_func):
140     """Escape a list of arguments by running the specified escape_func
141     on every object in the list that has an escape() method."""
142     def escape(obj, escape_func=escape_func):
143         try:
144             e = obj.escape
145         except AttributeError:
146             return obj
147         else:
148             return e(escape_func)
149     return map(escape, list)
150
151 class NLWrapper:
152     """A wrapper class that delays turning a list of sources or targets
153     into a NodeList until it's needed.  The specified function supplied
154     when the object is initialized is responsible for turning raw nodes
155     into proxies that implement the special attributes like .abspath,
156     .source, etc.  This way, we avoid creating those proxies just
157     "in case" someone is going to use $TARGET or the like, and only
158     go through the trouble if we really have to.
159
160     In practice, this might be a wash performance-wise, but it's a little
161     cleaner conceptually...
162     """
163     
164     def __init__(self, list, func):
165         self.list = list
166         self.func = func
167     def _return_nodelist(self):
168         return self.nodelist
169     def _gen_nodelist(self):
170         list = self.list
171         if list is None:
172             list = []
173         elif not is_List(list) and not is_Tuple(list):
174             list = [list]
175         # The map(self.func) call is what actually turns
176         # a list into appropriate proxies.
177         self.nodelist = SCons.Util.NodeList(map(self.func, list))
178         self._create_nodelist = self._return_nodelist
179         return self.nodelist
180     _create_nodelist = _gen_nodelist
181     
182
183 class Targets_or_Sources(UserList.UserList):
184     """A class that implements $TARGETS or $SOURCES expansions by in turn
185     wrapping a NLWrapper.  This class handles the different methods used
186     to access the list, calling the NLWrapper to create proxies on demand.
187
188     Note that we subclass UserList.UserList purely so that the is_List()
189     function will identify an object of this class as a list during
190     variable expansion.  We're not really using any UserList.UserList
191     methods in practice.
192     """
193     def __init__(self, nl):
194         self.nl = nl
195     def __getattr__(self, attr):
196         nl = self.nl._create_nodelist()
197         return getattr(nl, attr)
198     def __getitem__(self, i):
199         nl = self.nl._create_nodelist()
200         return nl[i]
201     def __getslice__(self, i, j):
202         nl = self.nl._create_nodelist()
203         i = max(i, 0); j = max(j, 0)
204         return nl[i:j]
205     def __str__(self):
206         nl = self.nl._create_nodelist()
207         return str(nl)
208     def __repr__(self):
209         nl = self.nl._create_nodelist()
210         return repr(nl)
211
212 class Target_or_Source:
213     """A class that implements $TARGET or $SOURCE expansions by in turn
214     wrapping a NLWrapper.  This class handles the different methods used
215     to access an individual proxy Node, calling the NLWrapper to create
216     a proxy on demand.
217     """
218     def __init__(self, nl):
219         self.nl = nl
220     def __getattr__(self, attr):
221         nl = self.nl._create_nodelist()
222         try:
223             nl0 = nl[0]
224         except IndexError:
225             # If there is nothing in the list, then we have no attributes to
226             # pass through, so raise AttributeError for everything.
227             raise AttributeError, "NodeList has no attribute: %s" % attr
228         return getattr(nl0, attr)
229     def __str__(self):
230         nl = self.nl._create_nodelist()
231         if nl:
232             return str(nl[0])
233         return ''
234     def __repr__(self):
235         nl = self.nl._create_nodelist()
236         if nl:
237             return repr(nl[0])
238         return ''
239
240 def subst_dict(target, source):
241     """Create a dictionary for substitution of special
242     construction variables.
243
244     This translates the following special arguments:
245
246     target - the target (object or array of objects),
247              used to generate the TARGET and TARGETS
248              construction variables
249
250     source - the source (object or array of objects),
251              used to generate the SOURCES and SOURCE
252              construction variables
253     """
254     dict = {}
255
256     if target:
257         tnl = NLWrapper(target, lambda x: x.get_subst_proxy())
258         dict['TARGETS'] = Targets_or_Sources(tnl)
259         dict['TARGET'] = Target_or_Source(tnl)
260     else:
261         dict['TARGETS'] = None
262         dict['TARGET'] = None
263
264     if source:
265         def get_src_subst_proxy(node):
266             try:
267                 rfile = node.rfile
268             except AttributeError:
269                 pass
270             else:
271                 node = rfile()
272             return node.get_subst_proxy()
273         snl = NLWrapper(source, get_src_subst_proxy)
274         dict['SOURCES'] = Targets_or_Sources(snl)
275         dict['SOURCE'] = Target_or_Source(snl)
276     else:
277         dict['SOURCES'] = None
278         dict['SOURCE'] = None
279
280     return dict
281
282 # Constants for the "mode" parameter to scons_subst_list() and
283 # scons_subst().  SUBST_RAW gives the raw command line.  SUBST_CMD
284 # gives a command line suitable for passing to a shell.  SUBST_SIG
285 # gives a command line appropriate for calculating the signature
286 # of a command line...if this changes, we should rebuild.
287 SUBST_CMD = 0
288 SUBST_RAW = 1
289 SUBST_SIG = 2
290
291 _rm = re.compile(r'\$[()]')
292 _remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)')
293
294 # Indexed by the SUBST_* constants above.
295 _regex_remove = [ _rm, None, _remove ]
296
297 # Regular expressions for splitting strings and handling substitutions,
298 # for use by the scons_subst() and scons_subst_list() functions:
299 #
300 # The first expression compiled matches all of the $-introduced tokens
301 # that we need to process in some way, and is used for substitutions.
302 # The expressions it matches are:
303 #
304 #       "$$"
305 #       "$("
306 #       "$)"
307 #       "$variable"             [must begin with alphabetic or underscore]
308 #       "${any stuff}"
309 #
310 # The second expression compiled is used for splitting strings into tokens
311 # to be processed, and it matches all of the tokens listed above, plus
312 # the following that affect how arguments do or don't get joined together:
313 #
314 #       "   "                   [white space]
315 #       "non-white-space"       [without any dollar signs]
316 #       "$"                     [single dollar sign]
317 #
318 _dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}'
319 _dollar_exps = re.compile(r'(%s)' % _dollar_exps_str)
320 _separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str)
321
322 # This regular expression is used to replace strings of multiple white
323 # space characters in the string result from the scons_subst() function.
324 _space_sep = re.compile(r'[\t ]+(?![^{]*})')
325
326 def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
327     """Expand a string containing construction variable substitutions.
328
329     This is the work-horse function for substitutions in file names
330     and the like.  The companion scons_subst_list() function (below)
331     handles separating command lines into lists of arguments, so see
332     that function if that's what you're looking for.
333     """
334     if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0:
335         return strSubst
336
337     class StringSubber:
338         """A class to construct the results of a scons_subst() call.
339
340         This binds a specific construction environment, mode, target and
341         source with two methods (substitute() and expand()) that handle
342         the expansion.
343         """
344         def __init__(self, env, mode, target, source, conv, gvars):
345             self.env = env
346             self.mode = mode
347             self.target = target
348             self.source = source
349             self.conv = conv
350             self.gvars = gvars
351
352         def expand(self, s, lvars):
353             """Expand a single "token" as necessary, returning an
354             appropriate string containing the expansion.
355
356             This handles expanding different types of things (strings,
357             lists, callables) appropriately.  It calls the wrapper
358             substitute() method to re-expand things as necessary, so that
359             the results of expansions of side-by-side strings still get
360             re-evaluated separately, not smushed together.
361             """
362             if is_String(s):
363                 try:
364                     s0, s1 = s[:2]
365                 except (IndexError, ValueError):
366                     return s
367                 if s0 != '$':
368                     return s
369                 if s1 == '$':
370                     return '$'
371                 elif s1 in '()':
372                     return s
373                 else:
374                     key = s[1:]
375                     if key[0] == '{' or string.find(key, '.') >= 0:
376                         if key[0] == '{':
377                             key = key[1:-1]
378                         try:
379                             s = eval(key, self.gvars, lvars)
380                         except AttributeError, e:
381                             raise SCons.Errors.UserError, \
382                                   "Error trying to evaluate `%s': %s" % (s, e)
383                         except (IndexError, NameError, TypeError):
384                             return ''
385                         except SyntaxError,e:
386                             if self.target:
387                                 raise SCons.Errors.BuildError, (self.target[0], "Syntax error `%s' trying to evaluate `%s'" % (e,s))
388                             else:
389                                 raise SCons.Errors.UserError, "Syntax error `%s' trying to evaluate `%s'" % (e,s)
390                     else:
391                         if lvars.has_key(key):
392                             s = lvars[key]
393                         elif self.gvars.has_key(key):
394                             s = self.gvars[key]
395                         else:
396                             return ''
397     
398                     # Before re-expanding the result, handle
399                     # recursive expansion by copying the local
400                     # variable dictionary and overwriting a null
401                     # string for the value of the variable name
402                     # we just expanded.
403                     #
404                     # This could potentially be optimized by only
405                     # copying lvars when s contains more expansions,
406                     # but lvars is usually supposed to be pretty
407                     # small, and deeply nested variable expansions
408                     # are probably more the exception than the norm,
409                     # so it should be tolerable for now.
410                     lv = lvars.copy()
411                     var = string.split(key, '.')[0]
412                     lv[var] = ''
413                     return self.substitute(s, lv)
414             elif is_List(s) or is_Tuple(s):
415                 def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
416                     return conv(substitute(l, lvars))
417                 r = map(func, s)
418                 return string.join(r)
419             elif callable(s):
420                 try:
421                     s = s(target=self.target,
422                          source=self.source,
423                          env=self.env,
424                          for_signature=(self.mode != SUBST_CMD))
425                 except TypeError:
426                     # This probably indicates that it's a callable
427                     # object that doesn't match our calling arguments
428                     # (like an Action).
429                     s = str(s)
430                 return self.substitute(s, lvars)
431             elif s is None:
432                 return ''
433             else:
434                 return s
435
436         def substitute(self, args, lvars):
437             """Substitute expansions in an argument or list of arguments.
438
439             This serves as a wrapper for splitting up a string into
440             separate tokens.
441             """
442             if is_String(args) and not isinstance(args, CmdStringHolder):
443                 try:
444                     def sub_match(match, conv=self.conv, expand=self.expand, lvars=lvars):
445                         return conv(expand(match.group(1), lvars))
446                     result = _dollar_exps.sub(sub_match, args)
447                 except TypeError:
448                     # If the internal conversion routine doesn't return
449                     # strings (it could be overridden to return Nodes, for
450                     # example), then the 1.5.2 re module will throw this
451                     # exception.  Back off to a slower, general-purpose
452                     # algorithm that works for all data types.
453                     args = _separate_args.findall(args)
454                     result = []
455                     for a in args:
456                         result.append(self.conv(self.expand(a, lvars)))
457                     try:
458                         result = string.join(result, '')
459                     except TypeError:
460                         if len(result) == 1:
461                             result = result[0]
462                 return result
463             else:
464                 return self.expand(args, lvars)
465
466     if conv is None:
467         conv = _strconv[mode]
468
469     # Doing this every time is a bit of a waste, since the Executor
470     # has typically already populated the OverrideEnvironment with
471     # $TARGET/$SOURCE variables.  We're keeping this (for now), though,
472     # because it supports existing behavior that allows us to call
473     # an Action directly with an arbitrary target+source pair, which
474     # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
475     # If we dropped that behavior (or found another way to cover it),
476     # we could get rid of this call completely and just rely on the
477     # Executor setting the variables.
478     d = subst_dict(target, source)
479     if d:
480         lvars = lvars.copy()
481         lvars.update(d)
482
483     # We're (most likely) going to eval() things.  If Python doesn't
484     # find a __builtin__ value in the global dictionary used for eval(),
485     # it copies the current __builtin__ values for you.  Avoid this by
486     # setting it explicitly and then deleting, so we don't pollute the
487     # construction environment Dictionary(ies) that are typically used
488     # for expansion.
489     gvars['__builtin__'] = __builtin__
490
491     ss = StringSubber(env, mode, target, source, conv, gvars)
492     result = ss.substitute(strSubst, lvars)
493
494     try:
495         del gvars['__builtin__']
496     except KeyError:
497         pass
498
499     if is_String(result):
500         # Remove $(-$) pairs and any stuff in between,
501         # if that's appropriate.
502         remove = _regex_remove[mode]
503         if remove:
504             result = remove.sub('', result)
505         if mode != SUBST_RAW:
506             # Compress strings of white space characters into
507             # a single space.
508             result = string.strip(_space_sep.sub(' ', result))
509
510     return result
511
512 #Subst_List_Strings = {}
513
514 def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
515     """Substitute construction variables in a string (or list or other
516     object) and separate the arguments into a command list.
517
518     The companion scons_subst() function (above) handles basic
519     substitutions within strings, so see that function instead
520     if that's what you're looking for.
521     """
522 #    try:
523 #        Subst_List_Strings[strSubst] = Subst_List_Strings[strSubst] + 1
524 #    except KeyError:
525 #        Subst_List_Strings[strSubst] = 1
526 #    import SCons.Debug
527 #    SCons.Debug.caller(1)
528     class ListSubber(UserList.UserList):
529         """A class to construct the results of a scons_subst_list() call.
530
531         Like StringSubber, this class binds a specific construction
532         environment, mode, target and source with two methods
533         (substitute() and expand()) that handle the expansion.
534
535         In addition, however, this class is used to track the state of
536         the result(s) we're gathering so we can do the appropriate thing
537         whenever we have to append another word to the result--start a new
538         line, start a new word, append to the current word, etc.  We do
539         this by setting the "append" attribute to the right method so
540         that our wrapper methods only need ever call ListSubber.append(),
541         and the rest of the object takes care of doing the right thing
542         internally.
543         """
544         def __init__(self, env, mode, target, source, conv, gvars):
545             UserList.UserList.__init__(self, [])
546             self.env = env
547             self.mode = mode
548             self.target = target
549             self.source = source
550             self.conv = conv
551             self.gvars = gvars
552
553             if self.mode == SUBST_RAW:
554                 self.add_strip = lambda x, s=self: s.append(x)
555             else:
556                 self.add_strip = lambda x, s=self: None
557             self.in_strip = None
558             self.next_line()
559
560         def expand(self, s, lvars, within_list):
561             """Expand a single "token" as necessary, appending the
562             expansion to the current result.
563
564             This handles expanding different types of things (strings,
565             lists, callables) appropriately.  It calls the wrapper
566             substitute() method to re-expand things as necessary, so that
567             the results of expansions of side-by-side strings still get
568             re-evaluated separately, not smushed together.
569             """
570
571             if is_String(s):
572                 try:
573                     s0, s1 = s[:2]
574                 except (IndexError, ValueError):
575                     self.append(s)
576                     return
577                 if s0 != '$':
578                     self.append(s)
579                     return
580                 if s1 == '$':
581                     self.append('$')
582                 elif s1 == '(':
583                     self.open_strip('$(')
584                 elif s1 == ')':
585                     self.close_strip('$)')
586                 else:
587                     key = s[1:]
588                     if key[0] == '{' or string.find(key, '.') >= 0:
589                         if key[0] == '{':
590                             key = key[1:-1]
591                         try:
592                             s = eval(key, self.gvars, lvars)
593                         except AttributeError, e:
594                             raise SCons.Errors.UserError, \
595                                   "Error trying to evaluate `%s': %s" % (s, e)
596                         except (IndexError, NameError, TypeError):
597                             return
598                         except SyntaxError,e:
599                             if self.target:
600                                 raise SCons.Errors.BuildError, (self.target[0], "Syntax error `%s' trying to evaluate `%s'" % (e,s))
601                             else:
602                                 raise SCons.Errors.UserError, "Syntax error `%s' trying to evaluate `%s'" % (e,s)
603                     else:
604                         if lvars.has_key(key):
605                             s = lvars[key]
606                         elif self.gvars.has_key(key):
607                             s = self.gvars[key]
608                         else:
609                             return
610
611                     # Before re-expanding the result, handle
612                     # recursive expansion by copying the local
613                     # variable dictionary and overwriting a null
614                     # string for the value of the variable name
615                     # we just expanded.
616                     lv = lvars.copy()
617                     var = string.split(key, '.')[0]
618                     lv[var] = ''
619                     self.substitute(s, lv, 0)
620                     self.this_word()
621             elif is_List(s) or is_Tuple(s):
622                 for a in s:
623                     self.substitute(a, lvars, 1)
624                     self.next_word()
625             elif callable(s):
626                 try:
627                     s = s(target=self.target,
628                          source=self.source,
629                          env=self.env,
630                          for_signature=(self.mode != SUBST_CMD))
631                 except TypeError:
632                     # This probably indicates that it's a callable
633                     # object that doesn't match our calling arguments
634                     # (like an Action).
635                     s = str(s)
636                 self.substitute(s, lvars, within_list)
637             elif s is None:
638                 self.this_word()
639             else:
640                 self.append(s)
641
642         def substitute(self, args, lvars, within_list):
643             """Substitute expansions in an argument or list of arguments.
644
645             This serves as a wrapper for splitting up a string into
646             separate tokens.
647             """
648
649             if is_String(args) and not isinstance(args, CmdStringHolder):
650                 args = _separate_args.findall(args)
651                 for a in args:
652                     if a[0] in ' \t\n\r\f\v':
653                         if '\n' in a:
654                             self.next_line()
655                         elif within_list:
656                             self.append(a)
657                         else:
658                             self.next_word()
659                     else:
660                         self.expand(a, lvars, within_list)
661             else:
662                 self.expand(args, lvars, within_list)
663
664         def next_line(self):
665             """Arrange for the next word to start a new line.  This
666             is like starting a new word, except that we have to append
667             another line to the result."""
668             UserList.UserList.append(self, [])
669             self.next_word()
670
671         def this_word(self):
672             """Arrange for the next word to append to the end of the
673             current last word in the result."""
674             self.append = self.add_to_current_word
675
676         def next_word(self):
677             """Arrange for the next word to start a new word."""
678             self.append = self.add_new_word
679
680         def add_to_current_word(self, x):
681             """Append the string x to the end of the current last word
682             in the result.  If that is not possible, then just add
683             it as a new word.  Make sure the entire concatenated string
684             inherits the object attributes of x (in particular, the
685             escape function) by wrapping it as CmdStringHolder."""
686
687             if not self.in_strip or self.mode != SUBST_SIG:
688                 try:
689                     current_word = self[-1][-1]
690                 except IndexError:
691                     self.add_new_word(x)
692                 else:
693                     # All right, this is a hack and it should probably
694                     # be refactored out of existence in the future.
695                     # The issue is that we want to smoosh words together
696                     # and make one file name that gets escaped if
697                     # we're expanding something like foo$EXTENSION,
698                     # but we don't want to smoosh them together if
699                     # it's something like >$TARGET, because then we'll
700                     # treat the '>' like it's part of the file name.
701                     # So for now, just hard-code looking for the special
702                     # command-line redirection characters...
703                     try:
704                         last_char = str(current_word)[-1]
705                     except IndexError:
706                         last_char = '\0'
707                     if last_char in '<>|':
708                         self.add_new_word(x)
709                     else:
710                         y = current_word + x
711
712                         # We used to treat a word appended to a literal
713                         # as a literal itself, but this caused problems
714                         # with interpreting quotes around space-separated
715                         # targets on command lines.  Removing this makes
716                         # none of the "substantive" end-to-end tests fail,
717                         # so we'll take this out but leave it commented
718                         # for now in case there's a problem not covered
719                         # by the test cases and we need to resurrect this.
720                         #literal1 = self.literal(self[-1][-1])
721                         #literal2 = self.literal(x)
722                         y = self.conv(y)
723                         if is_String(y):
724                             #y = CmdStringHolder(y, literal1 or literal2)
725                             y = CmdStringHolder(y, None)
726                         self[-1][-1] = y
727
728         def add_new_word(self, x):
729             if not self.in_strip or self.mode != SUBST_SIG:
730                 literal = self.literal(x)
731                 x = self.conv(x)
732                 if is_String(x):
733                     x = CmdStringHolder(x, literal)
734                 self[-1].append(x)
735             self.append = self.add_to_current_word
736
737         def literal(self, x):
738             try:
739                 l = x.is_literal
740             except AttributeError:
741                 return None
742             else:
743                 return l()
744
745         def open_strip(self, x):
746             """Handle the "open strip" $( token."""
747             self.add_strip(x)
748             self.in_strip = 1
749
750         def close_strip(self, x):
751             """Handle the "close strip" $) token."""
752             self.add_strip(x)
753             self.in_strip = None
754
755     if conv is None:
756         conv = _strconv[mode]
757
758     # Doing this every time is a bit of a waste, since the Executor
759     # has typically already populated the OverrideEnvironment with
760     # $TARGET/$SOURCE variables.  We're keeping this (for now), though,
761     # because it supports existing behavior that allows us to call
762     # an Action directly with an arbitrary target+source pair, which
763     # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
764     # If we dropped that behavior (or found another way to cover it),
765     # we could get rid of this call completely and just rely on the
766     # Executor setting the variables.
767     d = subst_dict(target, source)
768     if d:
769         lvars = lvars.copy()
770         lvars.update(d)
771
772     # We're (most likely) going to eval() things.  If Python doesn't
773     # find a __builtin__ value in the global dictionary used for eval(),
774     # it copies the current __builtin__ values for you.  Avoid this by
775     # setting it explicitly and then deleting, so we don't pollute the
776     # construction environment Dictionary(ies) that are typically used
777     # for expansion.
778     gvars['__builtins__'] = __builtins__
779
780     ls = ListSubber(env, mode, target, source, conv, gvars)
781     ls.substitute(strSubst, lvars, 0)
782
783     try:
784         del gvars['__builtins__']
785     except KeyError:
786         pass
787
788     return ls.data
789
790 def scons_subst_once(strSubst, env, key):
791     """Perform single (non-recursive) substitution of a single
792     construction variable keyword.
793
794     This is used when setting a variable when copying or overriding values
795     in an Environment.  We want to capture (expand) the old value before
796     we override it, so people can do things like:
797
798         env2 = env.Clone(CCFLAGS = '$CCFLAGS -g')
799
800     We do this with some straightforward, brute-force code here...
801     """
802     if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0:
803         return strSubst
804
805     matchlist = ['$' + key, '${' + key + '}']
806     val = env.get(key, '')
807     def sub_match(match, val=val, matchlist=matchlist):
808         a = match.group(1)
809         if a in matchlist:
810             a = val
811         if is_List(a) or is_Tuple(a):
812             return string.join(map(str, a))
813         else:
814             return str(a)
815
816     if is_List(strSubst) or is_Tuple(strSubst):
817         result = []
818         for arg in strSubst:
819             if is_String(arg):
820                 if arg in matchlist:
821                     arg = val
822                     if is_List(arg) or is_Tuple(arg):
823                         result.extend(arg)
824                     else:
825                         result.append(arg)
826                 else:
827                     result.append(_dollar_exps.sub(sub_match, arg))
828             else:
829                 result.append(arg)
830         return result
831     elif is_String(strSubst):
832         return _dollar_exps.sub(sub_match, strSubst)
833     else:
834         return strSubst