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.
29 from __future__ import generators ### KEEP FOR COMPATIBILITY FIXERS
31 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
38 from SCons.Util import is_String, is_Sequence
40 # Indexed by the SUBST_* constants below.
41 _strconv = [SCons.Util.to_String_for_subst,
42 SCons.Util.to_String_for_subst,
43 SCons.Util.to_String_for_signature]
47 AllowableExceptions = (IndexError, NameError)
49 def SetAllowableExceptions(*excepts):
50 global AllowableExceptions
51 AllowableExceptions = [_f for _f in excepts if _f]
53 def raise_exception(exception, target, s):
54 name = exception.__class__.__name__
55 msg = "%s `%s' trying to evaluate `%s'" % (name, exception, s)
57 raise SCons.Errors.BuildError, (target[0], msg)
59 raise SCons.Errors.UserError, msg
64 """A wrapper for a string. If you use this object wrapped
65 around a string, then it will be interpreted as literal.
66 When passed to the command interpreter, all special
67 characters will be escaped."""
68 def __init__(self, lstr):
74 def escape(self, escape_func):
75 return escape_func(self.lstr)
77 def for_signature(self):
83 class SpecialAttrWrapper:
84 """This is a wrapper for what we call a 'Node special attribute.'
85 This is any of the attributes of a Node that we can reference from
86 Environment variable substitution, such as $TARGET.abspath or
87 $SOURCES[1].filebase. We implement the same methods as Literal
88 so we can handle special characters, plus a for_signature method,
89 such that we can return some canonical string during signature
90 calculation to avoid unnecessary rebuilds."""
92 def __init__(self, lstr, for_signature=None):
93 """The for_signature parameter, if supplied, will be the
94 canonical string we return from for_signature(). Else
95 we will simply return lstr."""
98 self.forsig = for_signature
105 def escape(self, escape_func):
106 return escape_func(self.lstr)
108 def for_signature(self):
111 def is_literal(self):
114 def quote_spaces(arg):
115 """Generic function for putting double quotes around any string that
116 has white space in it."""
117 if ' ' in arg or '\t' in arg:
122 class CmdStringHolder(collections.UserString):
123 """This is a special class used to hold strings generated by
124 scons_subst() and scons_subst_list(). It defines a special method
125 escape(). When passed a function with an escape algorithm for a
126 particular platform, it will return the contained string with the
127 proper escape sequences inserted.
129 def __init__(self, cmd, literal=None):
130 collections.UserString.__init__(self, cmd)
131 self.literal = literal
133 def is_literal(self):
136 def escape(self, escape_func, quote_func=quote_spaces):
137 """Escape the string with the supplied function. The
138 function is expected to take an arbitrary string, then
139 return it with all special characters escaped and ready
140 for passing to the command interpreter.
142 After calling this function, the next call to str() will
143 return the escaped string.
146 if self.is_literal():
147 return escape_func(self.data)
148 elif ' ' in self.data or '\t' in self.data:
149 return quote_func(self.data)
153 def escape_list(mylist, escape_func):
154 """Escape a list of arguments by running the specified escape_func
155 on every object in the list that has an escape() method."""
156 def escape(obj, escape_func=escape_func):
159 except AttributeError:
162 return e(escape_func)
163 return list(map(escape, mylist))
166 """A wrapper class that delays turning a list of sources or targets
167 into a NodeList until it's needed. The specified function supplied
168 when the object is initialized is responsible for turning raw nodes
169 into proxies that implement the special attributes like .abspath,
170 .source, etc. This way, we avoid creating those proxies just
171 "in case" someone is going to use $TARGET or the like, and only
172 go through the trouble if we really have to.
174 In practice, this might be a wash performance-wise, but it's a little
175 cleaner conceptually...
178 def __init__(self, list, func):
181 def _return_nodelist(self):
183 def _gen_nodelist(self):
187 elif not is_Sequence(mylist):
189 # The map(self.func) call is what actually turns
190 # a list into appropriate proxies.
191 self.nodelist = SCons.Util.NodeList(list(map(self.func, mylist)))
192 self._create_nodelist = self._return_nodelist
194 _create_nodelist = _gen_nodelist
197 class Targets_or_Sources(collections.UserList):
198 """A class that implements $TARGETS or $SOURCES expansions by in turn
199 wrapping a NLWrapper. This class handles the different methods used
200 to access the list, calling the NLWrapper to create proxies on demand.
202 Note that we subclass collections.UserList purely so that the
203 is_Sequence() function will identify an object of this class as
204 a list during variable expansion. We're not really using any
205 collections.UserList methods in practice.
207 def __init__(self, nl):
209 def __getattr__(self, attr):
210 nl = self.nl._create_nodelist()
211 return getattr(nl, attr)
212 def __getitem__(self, i):
213 nl = self.nl._create_nodelist()
215 def __getslice__(self, i, j):
216 nl = self.nl._create_nodelist()
217 i = max(i, 0); j = max(j, 0)
220 nl = self.nl._create_nodelist()
223 nl = self.nl._create_nodelist()
226 class Target_or_Source:
227 """A class that implements $TARGET or $SOURCE expansions by in turn
228 wrapping a NLWrapper. This class handles the different methods used
229 to access an individual proxy Node, calling the NLWrapper to create
232 def __init__(self, nl):
234 def __getattr__(self, attr):
235 nl = self.nl._create_nodelist()
239 # If there is nothing in the list, then we have no attributes to
240 # pass through, so raise AttributeError for everything.
241 raise AttributeError, "NodeList has no attribute: %s" % attr
242 return getattr(nl0, attr)
244 nl = self.nl._create_nodelist()
249 nl = self.nl._create_nodelist()
254 class NullNodeList(SCons.Util.NullSeq):
255 def __call__(self, *args, **kwargs): return ''
256 def __str__(self): return ''
257 # TODO(1.5): unneeded after new-style classes introduce iterators
258 def __getitem__(self, i):
261 NullNodesList = NullNodeList()
263 def subst_dict(target, source):
264 """Create a dictionary for substitution of special
265 construction variables.
267 This translates the following special arguments:
269 target - the target (object or array of objects),
270 used to generate the TARGET and TARGETS
271 construction variables
273 source - the source (object or array of objects),
274 used to generate the SOURCES and SOURCE
275 construction variables
280 def get_tgt_subst_proxy(thing):
282 subst_proxy = thing.get_subst_proxy()
283 except AttributeError:
284 subst_proxy = thing # probably a string, just return it
286 tnl = NLWrapper(target, get_tgt_subst_proxy)
287 dict['TARGETS'] = Targets_or_Sources(tnl)
288 dict['TARGET'] = Target_or_Source(tnl)
290 # This is a total cheat, but hopefully this dictionary goes
291 # away soon anyway. We just let these expand to $TARGETS
292 # because that's "good enough" for the use of ToolSurrogates
293 # (see test/ToolSurrogate.py) to generate documentation.
294 dict['CHANGED_TARGETS'] = '$TARGETS'
295 dict['UNCHANGED_TARGETS'] = '$TARGETS'
297 dict['TARGETS'] = NullNodesList
298 dict['TARGET'] = NullNodesList
301 def get_src_subst_proxy(node):
304 except AttributeError:
309 return node.get_subst_proxy()
310 except AttributeError:
311 return node # probably a String, just return it
312 snl = NLWrapper(source, get_src_subst_proxy)
313 dict['SOURCES'] = Targets_or_Sources(snl)
314 dict['SOURCE'] = Target_or_Source(snl)
316 # This is a total cheat, but hopefully this dictionary goes
317 # away soon anyway. We just let these expand to $TARGETS
318 # because that's "good enough" for the use of ToolSurrogates
319 # (see test/ToolSurrogate.py) to generate documentation.
320 dict['CHANGED_SOURCES'] = '$SOURCES'
321 dict['UNCHANGED_SOURCES'] = '$SOURCES'
323 dict['SOURCES'] = NullNodesList
324 dict['SOURCE'] = NullNodesList
328 # Constants for the "mode" parameter to scons_subst_list() and
329 # scons_subst(). SUBST_RAW gives the raw command line. SUBST_CMD
330 # gives a command line suitable for passing to a shell. SUBST_SIG
331 # gives a command line appropriate for calculating the signature
332 # of a command line...if this changes, we should rebuild.
337 _rm = re.compile(r'\$[()]')
338 _remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)')
340 # Indexed by the SUBST_* constants above.
341 _regex_remove = [ _rm, None, _remove ]
344 #return [ l for l in list if not l in ('$(', '$)') ]
345 return [l for l in list if not l in ('$(', '$)')]
347 def _remove_list(list):
349 do_append = result.append
352 do_append = lambda x: None
354 do_append = result.append
359 # Indexed by the SUBST_* constants above.
360 _list_remove = [ _rm_list, None, _remove_list ]
362 # Regular expressions for splitting strings and handling substitutions,
363 # for use by the scons_subst() and scons_subst_list() functions:
365 # The first expression compiled matches all of the $-introduced tokens
366 # that we need to process in some way, and is used for substitutions.
367 # The expressions it matches are:
372 # "$variable" [must begin with alphabetic or underscore]
375 # The second expression compiled is used for splitting strings into tokens
376 # to be processed, and it matches all of the tokens listed above, plus
377 # the following that affect how arguments do or don't get joined together:
380 # "non-white-space" [without any dollar signs]
381 # "$" [single dollar sign]
383 _dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}'
384 _dollar_exps = re.compile(r'(%s)' % _dollar_exps_str)
385 _separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str)
387 # This regular expression is used to replace strings of multiple white
388 # space characters in the string result from the scons_subst() function.
389 _space_sep = re.compile(r'[\t ]+(?![^{]*})')
391 def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
392 """Expand a string or list containing construction variable
395 This is the work-horse function for substitutions in file names
396 and the like. The companion scons_subst_list() function (below)
397 handles separating command lines into lists of arguments, so see
398 that function if that's what you're looking for.
400 if isinstance(strSubst, str) and strSubst.find('$') < 0:
404 """A class to construct the results of a scons_subst() call.
406 This binds a specific construction environment, mode, target and
407 source with two methods (substitute() and expand()) that handle
410 def __init__(self, env, mode, conv, gvars):
416 def expand(self, s, lvars):
417 """Expand a single "token" as necessary, returning an
418 appropriate string containing the expansion.
420 This handles expanding different types of things (strings,
421 lists, callables) appropriately. It calls the wrapper
422 substitute() method to re-expand things as necessary, so that
423 the results of expansions of side-by-side strings still get
424 re-evaluated separately, not smushed together.
429 except (IndexError, ValueError):
439 if key[0] == '{' or key.find('.') >= 0:
443 s = eval(key, self.gvars, lvars)
444 except KeyboardInterrupt:
447 if e.__class__ in AllowableExceptions:
449 raise_exception(e, lvars['TARGETS'], s)
453 elif key in self.gvars:
455 elif not NameError in AllowableExceptions:
456 raise_exception(NameError(key), lvars['TARGETS'], s)
460 # Before re-expanding the result, handle
461 # recursive expansion by copying the local
462 # variable dictionary and overwriting a null
463 # string for the value of the variable name
466 # This could potentially be optimized by only
467 # copying lvars when s contains more expansions,
468 # but lvars is usually supposed to be pretty
469 # small, and deeply nested variable expansions
470 # are probably more the exception than the norm,
471 # so it should be tolerable for now.
473 var = key.split('.')[0]
475 return self.substitute(s, lv)
477 def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
478 return conv(substitute(l, lvars))
479 return list(map(func, s))
482 s = s(target=lvars['TARGETS'],
483 source=lvars['SOURCES'],
485 for_signature=(self.mode != SUBST_CMD))
487 # This probably indicates that it's a callable
488 # object that doesn't match our calling arguments
490 if self.mode == SUBST_RAW:
493 return self.substitute(s, lvars)
499 def substitute(self, args, lvars):
500 """Substitute expansions in an argument or list of arguments.
502 This serves as a wrapper for splitting up a string into
505 if is_String(args) and not isinstance(args, CmdStringHolder):
506 args = str(args) # In case it's a UserString.
508 def sub_match(match, conv=self.conv, expand=self.expand, lvars=lvars):
509 return conv(expand(match.group(1), lvars))
510 result = _dollar_exps.sub(sub_match, args)
512 # If the internal conversion routine doesn't return
513 # strings (it could be overridden to return Nodes, for
514 # example), then the 1.5.2 re module will throw this
515 # exception. Back off to a slower, general-purpose
516 # algorithm that works for all data types.
517 args = _separate_args.findall(args)
520 result.append(self.conv(self.expand(a, lvars)))
524 result = ''.join(map(str, result))
527 return self.expand(args, lvars)
530 conv = _strconv[mode]
532 # Doing this every time is a bit of a waste, since the Executor
533 # has typically already populated the OverrideEnvironment with
534 # $TARGET/$SOURCE variables. We're keeping this (for now), though,
535 # because it supports existing behavior that allows us to call
536 # an Action directly with an arbitrary target+source pair, which
537 # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
538 # If we dropped that behavior (or found another way to cover it),
539 # we could get rid of this call completely and just rely on the
540 # Executor setting the variables.
541 if 'TARGET' not in lvars:
542 d = subst_dict(target, source)
547 # We're (most likely) going to eval() things. If Python doesn't
548 # find a __builtins__ value in the global dictionary used for eval(),
549 # it copies the current global values for you. Avoid this by
550 # setting it explicitly and then deleting, so we don't pollute the
551 # construction environment Dictionary(ies) that are typically used
553 gvars['__builtins__'] = __builtins__
555 ss = StringSubber(env, mode, conv, gvars)
556 result = ss.substitute(strSubst, lvars)
559 del gvars['__builtins__']
563 if is_String(result):
564 # Remove $(-$) pairs and any stuff in between,
565 # if that's appropriate.
566 remove = _regex_remove[mode]
568 result = remove.sub('', result)
569 if mode != SUBST_RAW:
570 # Compress strings of white space characters into
572 result = _space_sep.sub(' ', result).strip()
573 elif is_Sequence(result):
574 remove = _list_remove[mode]
576 result = remove(result)
580 #Subst_List_Strings = {}
582 def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
583 """Substitute construction variables in a string (or list or other
584 object) and separate the arguments into a command list.
586 The companion scons_subst() function (above) handles basic
587 substitutions within strings, so see that function instead
588 if that's what you're looking for.
591 # Subst_List_Strings[strSubst] = Subst_List_Strings[strSubst] + 1
593 # Subst_List_Strings[strSubst] = 1
595 # SCons.Debug.caller_trace(1)
596 class ListSubber(collections.UserList):
597 """A class to construct the results of a scons_subst_list() call.
599 Like StringSubber, this class binds a specific construction
600 environment, mode, target and source with two methods
601 (substitute() and expand()) that handle the expansion.
603 In addition, however, this class is used to track the state of
604 the result(s) we're gathering so we can do the appropriate thing
605 whenever we have to append another word to the result--start a new
606 line, start a new word, append to the current word, etc. We do
607 this by setting the "append" attribute to the right method so
608 that our wrapper methods only need ever call ListSubber.append(),
609 and the rest of the object takes care of doing the right thing
612 def __init__(self, env, mode, conv, gvars):
613 collections.UserList.__init__(self, [])
619 if self.mode == SUBST_RAW:
620 self.add_strip = lambda x: self.append(x)
622 self.add_strip = lambda x: None
626 def expand(self, s, lvars, within_list):
627 """Expand a single "token" as necessary, appending the
628 expansion to the current result.
630 This handles expanding different types of things (strings,
631 lists, callables) appropriately. It calls the wrapper
632 substitute() method to re-expand things as necessary, so that
633 the results of expansions of side-by-side strings still get
634 re-evaluated separately, not smushed together.
640 except (IndexError, ValueError):
649 self.open_strip('$(')
651 self.close_strip('$)')
654 if key[0] == '{' or key.find('.') >= 0:
658 s = eval(key, self.gvars, lvars)
659 except KeyboardInterrupt:
662 if e.__class__ in AllowableExceptions:
664 raise_exception(e, lvars['TARGETS'], s)
668 elif key in self.gvars:
670 elif not NameError in AllowableExceptions:
671 raise_exception(NameError(), lvars['TARGETS'], s)
675 # Before re-expanding the result, handle
676 # recursive expansion by copying the local
677 # variable dictionary and overwriting a null
678 # string for the value of the variable name
681 var = key.split('.')[0]
683 self.substitute(s, lv, 0)
687 self.substitute(a, lvars, 1)
691 s = s(target=lvars['TARGETS'],
692 source=lvars['SOURCES'],
694 for_signature=(self.mode != SUBST_CMD))
696 # This probably indicates that it's a callable
697 # object that doesn't match our calling arguments
699 if self.mode == SUBST_RAW:
703 self.substitute(s, lvars, within_list)
709 def substitute(self, args, lvars, within_list):
710 """Substitute expansions in an argument or list of arguments.
712 This serves as a wrapper for splitting up a string into
716 if is_String(args) and not isinstance(args, CmdStringHolder):
717 args = str(args) # In case it's a UserString.
718 args = _separate_args.findall(args)
720 if a[0] in ' \t\n\r\f\v':
728 self.expand(a, lvars, within_list)
730 self.expand(args, lvars, within_list)
733 """Arrange for the next word to start a new line. This
734 is like starting a new word, except that we have to append
735 another line to the result."""
736 collections.UserList.append(self, [])
740 """Arrange for the next word to append to the end of the
741 current last word in the result."""
742 self.append = self.add_to_current_word
745 """Arrange for the next word to start a new word."""
746 self.append = self.add_new_word
748 def add_to_current_word(self, x):
749 """Append the string x to the end of the current last word
750 in the result. If that is not possible, then just add
751 it as a new word. Make sure the entire concatenated string
752 inherits the object attributes of x (in particular, the
753 escape function) by wrapping it as CmdStringHolder."""
755 if not self.in_strip or self.mode != SUBST_SIG:
757 current_word = self[-1][-1]
761 # All right, this is a hack and it should probably
762 # be refactored out of existence in the future.
763 # The issue is that we want to smoosh words together
764 # and make one file name that gets escaped if
765 # we're expanding something like foo$EXTENSION,
766 # but we don't want to smoosh them together if
767 # it's something like >$TARGET, because then we'll
768 # treat the '>' like it's part of the file name.
769 # So for now, just hard-code looking for the special
770 # command-line redirection characters...
772 last_char = str(current_word)[-1]
775 if last_char in '<>|':
780 # We used to treat a word appended to a literal
781 # as a literal itself, but this caused problems
782 # with interpreting quotes around space-separated
783 # targets on command lines. Removing this makes
784 # none of the "substantive" end-to-end tests fail,
785 # so we'll take this out but leave it commented
786 # for now in case there's a problem not covered
787 # by the test cases and we need to resurrect this.
788 #literal1 = self.literal(self[-1][-1])
789 #literal2 = self.literal(x)
792 #y = CmdStringHolder(y, literal1 or literal2)
793 y = CmdStringHolder(y, None)
796 def add_new_word(self, x):
797 if not self.in_strip or self.mode != SUBST_SIG:
798 literal = self.literal(x)
801 x = CmdStringHolder(x, literal)
803 self.append = self.add_to_current_word
805 def literal(self, x):
808 except AttributeError:
813 def open_strip(self, x):
814 """Handle the "open strip" $( token."""
818 def close_strip(self, x):
819 """Handle the "close strip" $) token."""
824 conv = _strconv[mode]
826 # Doing this every time is a bit of a waste, since the Executor
827 # has typically already populated the OverrideEnvironment with
828 # $TARGET/$SOURCE variables. We're keeping this (for now), though,
829 # because it supports existing behavior that allows us to call
830 # an Action directly with an arbitrary target+source pair, which
831 # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
832 # If we dropped that behavior (or found another way to cover it),
833 # we could get rid of this call completely and just rely on the
834 # Executor setting the variables.
835 if 'TARGET' not in lvars:
836 d = subst_dict(target, source)
841 # We're (most likely) going to eval() things. If Python doesn't
842 # find a __builtins__ value in the global dictionary used for eval(),
843 # it copies the current global values for you. Avoid this by
844 # setting it explicitly and then deleting, so we don't pollute the
845 # construction environment Dictionary(ies) that are typically used
847 gvars['__builtins__'] = __builtins__
849 ls = ListSubber(env, mode, conv, gvars)
850 ls.substitute(strSubst, lvars, 0)
853 del gvars['__builtins__']
859 def scons_subst_once(strSubst, env, key):
860 """Perform single (non-recursive) substitution of a single
861 construction variable keyword.
863 This is used when setting a variable when copying or overriding values
864 in an Environment. We want to capture (expand) the old value before
865 we override it, so people can do things like:
867 env2 = env.Clone(CCFLAGS = '$CCFLAGS -g')
869 We do this with some straightforward, brute-force code here...
871 if isinstance(strSubst, str) and strSubst.find('$') < 0:
874 matchlist = ['$' + key, '${' + key + '}']
875 val = env.get(key, '')
876 def sub_match(match, val=val, matchlist=matchlist):
881 return ' '.join(map(str, a))
885 if is_Sequence(strSubst):
896 result.append(_dollar_exps.sub(sub_match, arg))
900 elif is_String(strSubst):
901 return _dollar_exps.sub(sub_match, strSubst)
907 # indent-tabs-mode:nil
909 # vim: set expandtab tabstop=4 shiftwidth=4: