3 SCons string substitution.
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:
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
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.
30 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
40 from SCons.Util import is_String, is_List, is_Tuple
42 # Indexed by the SUBST_* constants below.
43 _strconv = [SCons.Util.to_String,
45 SCons.Util.to_String_for_signature]
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):
58 def escape(self, escape_func):
59 return escape_func(self.lstr)
61 def for_signature(self):
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."""
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."""
82 self.forsig = for_signature
89 def escape(self, escape_func):
90 return escape_func(self.lstr)
92 def for_signature(self):
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:
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.
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
119 def is_literal(self):
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.
128 After calling this function, the next call to str() will
129 return the escaped string.
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)
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):
145 except AttributeError:
148 return e(escape_func)
149 return map(escape, list)
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.
160 In practice, this might be a wash performance-wise, but it's a little
161 cleaner conceptually...
164 def __init__(self, list, func):
167 def _return_nodelist(self):
169 def _gen_nodelist(self):
173 elif not is_List(list) and not is_Tuple(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
180 _create_nodelist = _gen_nodelist
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.
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
193 def __init__(self, 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()
201 def __getslice__(self, i, j):
202 nl = self.nl._create_nodelist()
203 i = max(i, 0); j = max(j, 0)
206 nl = self.nl._create_nodelist()
209 nl = self.nl._create_nodelist()
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
218 def __init__(self, nl):
220 def __getattr__(self, attr):
221 nl = self.nl._create_nodelist()
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)
230 nl = self.nl._create_nodelist()
235 nl = self.nl._create_nodelist()
240 def subst_dict(target, source):
241 """Create a dictionary for substitution of special
242 construction variables.
244 This translates the following special arguments:
246 target - the target (object or array of objects),
247 used to generate the TARGET and TARGETS
248 construction variables
250 source - the source (object or array of objects),
251 used to generate the SOURCES and SOURCE
252 construction variables
257 tnl = NLWrapper(target, lambda x: x.get_subst_proxy())
258 dict['TARGETS'] = Targets_or_Sources(tnl)
259 dict['TARGET'] = Target_or_Source(tnl)
261 dict['TARGETS'] = None
262 dict['TARGET'] = None
265 def get_src_subst_proxy(node):
268 except AttributeError:
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)
277 dict['SOURCES'] = None
278 dict['SOURCE'] = None
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.
291 _rm = re.compile(r'\$[()]')
292 _remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)')
294 # Indexed by the SUBST_* constants above.
295 _regex_remove = [ _rm, None, _remove ]
297 # Regular expressions for splitting strings and handling substitutions,
298 # for use by the scons_subst() and scons_subst_list() functions:
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:
307 # "$variable" [must begin with alphabetic or underscore]
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:
315 # "non-white-space" [without any dollar signs]
316 # "$" [single dollar sign]
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)
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 ]+(?![^{]*})')
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.
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.
334 if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0:
338 """A class to construct the results of a scons_subst() call.
340 This binds a specific construction environment, mode, target and
341 source with two methods (substitute() and expand()) that handle
344 def __init__(self, env, mode, target, source, conv, gvars):
352 def expand(self, s, lvars):
353 """Expand a single "token" as necessary, returning an
354 appropriate string containing the expansion.
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.
365 except (IndexError, ValueError):
375 if key[0] == '{' or string.find(key, '.') >= 0:
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):
385 except SyntaxError,e:
387 raise SCons.Errors.BuildError, (self.target[0], "Syntax error `%s' trying to evaluate `%s'" % (e,s))
389 raise SCons.Errors.UserError, "Syntax error `%s' trying to evaluate `%s'" % (e,s)
391 if lvars.has_key(key):
393 elif self.gvars.has_key(key):
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
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.
411 var = string.split(key, '.')[0]
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))
418 return string.join(r)
421 s = s(target=self.target,
424 for_signature=(self.mode != SUBST_CMD))
426 # This probably indicates that it's a callable
427 # object that doesn't match our calling arguments
430 return self.substitute(s, lvars)
436 def substitute(self, args, lvars):
437 """Substitute expansions in an argument or list of arguments.
439 This serves as a wrapper for splitting up a string into
442 if is_String(args) and not isinstance(args, CmdStringHolder):
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)
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)
456 result.append(self.conv(self.expand(a, lvars)))
458 result = string.join(result, '')
464 return self.expand(args, lvars)
467 conv = _strconv[mode]
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)
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
489 gvars['__builtin__'] = __builtin__
491 ss = StringSubber(env, mode, target, source, conv, gvars)
492 result = ss.substitute(strSubst, lvars)
495 del gvars['__builtin__']
499 if is_String(result):
500 # Remove $(-$) pairs and any stuff in between,
501 # if that's appropriate.
502 remove = _regex_remove[mode]
504 result = remove.sub('', result)
505 if mode != SUBST_RAW:
506 # Compress strings of white space characters into
508 result = string.strip(_space_sep.sub(' ', result))
512 #Subst_List_Strings = {}
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.
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.
523 # Subst_List_Strings[strSubst] = Subst_List_Strings[strSubst] + 1
525 # Subst_List_Strings[strSubst] = 1
527 # SCons.Debug.caller(1)
528 class ListSubber(UserList.UserList):
529 """A class to construct the results of a scons_subst_list() call.
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.
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
544 def __init__(self, env, mode, target, source, conv, gvars):
545 UserList.UserList.__init__(self, [])
553 if self.mode == SUBST_RAW:
554 self.add_strip = lambda x, s=self: s.append(x)
556 self.add_strip = lambda x, s=self: None
560 def expand(self, s, lvars, within_list):
561 """Expand a single "token" as necessary, appending the
562 expansion to the current result.
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.
574 except (IndexError, ValueError):
583 self.open_strip('$(')
585 self.close_strip('$)')
588 if key[0] == '{' or string.find(key, '.') >= 0:
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):
598 except SyntaxError,e:
600 raise SCons.Errors.BuildError, (self.target[0], "Syntax error `%s' trying to evaluate `%s'" % (e,s))
602 raise SCons.Errors.UserError, "Syntax error `%s' trying to evaluate `%s'" % (e,s)
604 if lvars.has_key(key):
606 elif self.gvars.has_key(key):
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
617 var = string.split(key, '.')[0]
619 self.substitute(s, lv, 0)
621 elif is_List(s) or is_Tuple(s):
623 self.substitute(a, lvars, 1)
627 s = s(target=self.target,
630 for_signature=(self.mode != SUBST_CMD))
632 # This probably indicates that it's a callable
633 # object that doesn't match our calling arguments
636 self.substitute(s, lvars, within_list)
642 def substitute(self, args, lvars, within_list):
643 """Substitute expansions in an argument or list of arguments.
645 This serves as a wrapper for splitting up a string into
649 if is_String(args) and not isinstance(args, CmdStringHolder):
650 args = _separate_args.findall(args)
652 if a[0] in ' \t\n\r\f\v':
660 self.expand(a, lvars, within_list)
662 self.expand(args, lvars, within_list)
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, [])
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
677 """Arrange for the next word to start a new word."""
678 self.append = self.add_new_word
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."""
687 if not self.in_strip or self.mode != SUBST_SIG:
689 current_word = self[-1][-1]
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...
704 last_char = str(current_word)[-1]
707 if last_char in '<>|':
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)
724 #y = CmdStringHolder(y, literal1 or literal2)
725 y = CmdStringHolder(y, None)
728 def add_new_word(self, x):
729 if not self.in_strip or self.mode != SUBST_SIG:
730 literal = self.literal(x)
733 x = CmdStringHolder(x, literal)
735 self.append = self.add_to_current_word
737 def literal(self, x):
740 except AttributeError:
745 def open_strip(self, x):
746 """Handle the "open strip" $( token."""
750 def close_strip(self, x):
751 """Handle the "close strip" $) token."""
756 conv = _strconv[mode]
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)
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
778 gvars['__builtins__'] = __builtins__
780 ls = ListSubber(env, mode, target, source, conv, gvars)
781 ls.substitute(strSubst, lvars, 0)
784 del gvars['__builtins__']
790 def scons_subst_once(strSubst, env, key):
791 """Perform single (non-recursive) substitution of a single
792 construction variable keyword.
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:
798 env2 = env.Clone(CCFLAGS = '$CCFLAGS -g')
800 We do this with some straightforward, brute-force code here...
802 if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0:
805 matchlist = ['$' + key, '${' + key + '}']
806 val = env.get(key, '')
807 def sub_match(match, val=val, matchlist=matchlist):
811 if is_List(a) or is_Tuple(a):
812 return string.join(map(str, a))
816 if is_List(strSubst) or is_Tuple(strSubst):
822 if is_List(arg) or is_Tuple(arg):
827 result.append(_dollar_exps.sub(sub_match, arg))
831 elif is_String(strSubst):
832 return _dollar_exps.sub(sub_match, strSubst)