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__"
39 from SCons.Util import is_String, is_Sequence
41 # Indexed by the SUBST_* constants below.
42 _strconv = [SCons.Util.to_String_for_subst,
43 SCons.Util.to_String_for_subst,
44 SCons.Util.to_String_for_signature]
48 AllowableExceptions = (IndexError, NameError)
50 def SetAllowableExceptions(*excepts):
51 global AllowableExceptions
52 AllowableExceptions = [_f for _f in excepts if _f]
54 def raise_exception(exception, target, s):
55 name = exception.__class__.__name__
56 msg = "%s `%s' trying to evaluate `%s'" % (name, exception, s)
58 raise SCons.Errors.BuildError, (target[0], msg)
60 raise SCons.Errors.UserError, msg
65 """A wrapper for a string. If you use this object wrapped
66 around a string, then it will be interpreted as literal.
67 When passed to the command interpreter, all special
68 characters will be escaped."""
69 def __init__(self, lstr):
75 def escape(self, escape_func):
76 return escape_func(self.lstr)
78 def for_signature(self):
84 class SpecialAttrWrapper:
85 """This is a wrapper for what we call a 'Node special attribute.'
86 This is any of the attributes of a Node that we can reference from
87 Environment variable substitution, such as $TARGET.abspath or
88 $SOURCES[1].filebase. We implement the same methods as Literal
89 so we can handle special characters, plus a for_signature method,
90 such that we can return some canonical string during signature
91 calculation to avoid unnecessary rebuilds."""
93 def __init__(self, lstr, for_signature=None):
94 """The for_signature parameter, if supplied, will be the
95 canonical string we return from for_signature(). Else
96 we will simply return lstr."""
99 self.forsig = for_signature
106 def escape(self, escape_func):
107 return escape_func(self.lstr)
109 def for_signature(self):
112 def is_literal(self):
115 def quote_spaces(arg):
116 """Generic function for putting double quotes around any string that
117 has white space in it."""
118 if ' ' in arg or '\t' in arg:
123 class CmdStringHolder(UserString.UserString):
124 """This is a special class used to hold strings generated by
125 scons_subst() and scons_subst_list(). It defines a special method
126 escape(). When passed a function with an escape algorithm for a
127 particular platform, it will return the contained string with the
128 proper escape sequences inserted.
130 def __init__(self, cmd, literal=None):
131 UserString.UserString.__init__(self, cmd)
132 self.literal = literal
134 def is_literal(self):
137 def escape(self, escape_func, quote_func=quote_spaces):
138 """Escape the string with the supplied function. The
139 function is expected to take an arbitrary string, then
140 return it with all special characters escaped and ready
141 for passing to the command interpreter.
143 After calling this function, the next call to str() will
144 return the escaped string.
147 if self.is_literal():
148 return escape_func(self.data)
149 elif ' ' in self.data or '\t' in self.data:
150 return quote_func(self.data)
154 def escape_list(mylist, escape_func):
155 """Escape a list of arguments by running the specified escape_func
156 on every object in the list that has an escape() method."""
157 def escape(obj, escape_func=escape_func):
160 except AttributeError:
163 return e(escape_func)
164 return list(map(escape, mylist))
167 """A wrapper class that delays turning a list of sources or targets
168 into a NodeList until it's needed. The specified function supplied
169 when the object is initialized is responsible for turning raw nodes
170 into proxies that implement the special attributes like .abspath,
171 .source, etc. This way, we avoid creating those proxies just
172 "in case" someone is going to use $TARGET or the like, and only
173 go through the trouble if we really have to.
175 In practice, this might be a wash performance-wise, but it's a little
176 cleaner conceptually...
179 def __init__(self, list, func):
182 def _return_nodelist(self):
184 def _gen_nodelist(self):
188 elif not is_Sequence(mylist):
190 # The map(self.func) call is what actually turns
191 # a list into appropriate proxies.
192 self.nodelist = SCons.Util.NodeList(list(map(self.func, mylist)))
193 self._create_nodelist = self._return_nodelist
195 _create_nodelist = _gen_nodelist
198 class Targets_or_Sources(UserList.UserList):
199 """A class that implements $TARGETS or $SOURCES expansions by in turn
200 wrapping a NLWrapper. This class handles the different methods used
201 to access the list, calling the NLWrapper to create proxies on demand.
203 Note that we subclass UserList.UserList purely so that the
204 is_Sequence() function will identify an object of this class as
205 a list during variable expansion. We're not really using any
206 UserList.UserList methods in practice.
208 def __init__(self, nl):
210 def __getattr__(self, attr):
211 nl = self.nl._create_nodelist()
212 return getattr(nl, attr)
213 def __getitem__(self, i):
214 nl = self.nl._create_nodelist()
216 def __getslice__(self, i, j):
217 nl = self.nl._create_nodelist()
218 i = max(i, 0); j = max(j, 0)
221 nl = self.nl._create_nodelist()
224 nl = self.nl._create_nodelist()
227 class Target_or_Source:
228 """A class that implements $TARGET or $SOURCE expansions by in turn
229 wrapping a NLWrapper. This class handles the different methods used
230 to access an individual proxy Node, calling the NLWrapper to create
233 def __init__(self, nl):
235 def __getattr__(self, attr):
236 nl = self.nl._create_nodelist()
240 # If there is nothing in the list, then we have no attributes to
241 # pass through, so raise AttributeError for everything.
242 raise AttributeError, "NodeList has no attribute: %s" % attr
243 return getattr(nl0, attr)
245 nl = self.nl._create_nodelist()
250 nl = self.nl._create_nodelist()
255 class NullNodeList(SCons.Util.NullSeq):
256 def __call__(self, *args, **kwargs): return ''
257 def __str__(self): return ''
258 # TODO(1.5): unneeded after new-style classes introduce iterators
259 def __getitem__(self, i):
262 NullNodesList = NullNodeList()
264 def subst_dict(target, source):
265 """Create a dictionary for substitution of special
266 construction variables.
268 This translates the following special arguments:
270 target - the target (object or array of objects),
271 used to generate the TARGET and TARGETS
272 construction variables
274 source - the source (object or array of objects),
275 used to generate the SOURCES and SOURCE
276 construction variables
281 def get_tgt_subst_proxy(thing):
283 subst_proxy = thing.get_subst_proxy()
284 except AttributeError:
285 subst_proxy = thing # probably a string, just return it
287 tnl = NLWrapper(target, get_tgt_subst_proxy)
288 dict['TARGETS'] = Targets_or_Sources(tnl)
289 dict['TARGET'] = Target_or_Source(tnl)
291 # This is a total cheat, but hopefully this dictionary goes
292 # away soon anyway. We just let these expand to $TARGETS
293 # because that's "good enough" for the use of ToolSurrogates
294 # (see test/ToolSurrogate.py) to generate documentation.
295 dict['CHANGED_TARGETS'] = '$TARGETS'
296 dict['UNCHANGED_TARGETS'] = '$TARGETS'
298 dict['TARGETS'] = NullNodesList
299 dict['TARGET'] = NullNodesList
302 def get_src_subst_proxy(node):
305 except AttributeError:
310 return node.get_subst_proxy()
311 except AttributeError:
312 return node # probably a String, just return it
313 snl = NLWrapper(source, get_src_subst_proxy)
314 dict['SOURCES'] = Targets_or_Sources(snl)
315 dict['SOURCE'] = Target_or_Source(snl)
317 # This is a total cheat, but hopefully this dictionary goes
318 # away soon anyway. We just let these expand to $TARGETS
319 # because that's "good enough" for the use of ToolSurrogates
320 # (see test/ToolSurrogate.py) to generate documentation.
321 dict['CHANGED_SOURCES'] = '$SOURCES'
322 dict['UNCHANGED_SOURCES'] = '$SOURCES'
324 dict['SOURCES'] = NullNodesList
325 dict['SOURCE'] = NullNodesList
329 # Constants for the "mode" parameter to scons_subst_list() and
330 # scons_subst(). SUBST_RAW gives the raw command line. SUBST_CMD
331 # gives a command line suitable for passing to a shell. SUBST_SIG
332 # gives a command line appropriate for calculating the signature
333 # of a command line...if this changes, we should rebuild.
338 _rm = re.compile(r'\$[()]')
339 _remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)')
341 # Indexed by the SUBST_* constants above.
342 _regex_remove = [ _rm, None, _remove ]
345 #return [ l for l in list if not l in ('$(', '$)') ]
346 return [l for l in list if not l in ('$(', '$)')]
348 def _remove_list(list):
350 do_append = result.append
353 do_append = lambda x: None
355 do_append = result.append
360 # Indexed by the SUBST_* constants above.
361 _list_remove = [ _rm_list, None, _remove_list ]
363 # Regular expressions for splitting strings and handling substitutions,
364 # for use by the scons_subst() and scons_subst_list() functions:
366 # The first expression compiled matches all of the $-introduced tokens
367 # that we need to process in some way, and is used for substitutions.
368 # The expressions it matches are:
373 # "$variable" [must begin with alphabetic or underscore]
376 # The second expression compiled is used for splitting strings into tokens
377 # to be processed, and it matches all of the tokens listed above, plus
378 # the following that affect how arguments do or don't get joined together:
381 # "non-white-space" [without any dollar signs]
382 # "$" [single dollar sign]
384 _dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}'
385 _dollar_exps = re.compile(r'(%s)' % _dollar_exps_str)
386 _separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str)
388 # This regular expression is used to replace strings of multiple white
389 # space characters in the string result from the scons_subst() function.
390 _space_sep = re.compile(r'[\t ]+(?![^{]*})')
392 def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
393 """Expand a string or list containing construction variable
396 This is the work-horse function for substitutions in file names
397 and the like. The companion scons_subst_list() function (below)
398 handles separating command lines into lists of arguments, so see
399 that function if that's what you're looking for.
401 if isinstance(strSubst, str) and strSubst.find('$') < 0:
405 """A class to construct the results of a scons_subst() call.
407 This binds a specific construction environment, mode, target and
408 source with two methods (substitute() and expand()) that handle
411 def __init__(self, env, mode, conv, gvars):
417 def expand(self, s, lvars):
418 """Expand a single "token" as necessary, returning an
419 appropriate string containing the expansion.
421 This handles expanding different types of things (strings,
422 lists, callables) appropriately. It calls the wrapper
423 substitute() method to re-expand things as necessary, so that
424 the results of expansions of side-by-side strings still get
425 re-evaluated separately, not smushed together.
430 except (IndexError, ValueError):
440 if key[0] == '{' or key.find('.') >= 0:
444 s = eval(key, self.gvars, lvars)
445 except KeyboardInterrupt:
448 if e.__class__ in AllowableExceptions:
450 raise_exception(e, lvars['TARGETS'], s)
454 elif key in self.gvars:
456 elif not NameError in AllowableExceptions:
457 raise_exception(NameError(key), lvars['TARGETS'], s)
461 # Before re-expanding the result, handle
462 # recursive expansion by copying the local
463 # variable dictionary and overwriting a null
464 # string for the value of the variable name
467 # This could potentially be optimized by only
468 # copying lvars when s contains more expansions,
469 # but lvars is usually supposed to be pretty
470 # small, and deeply nested variable expansions
471 # are probably more the exception than the norm,
472 # so it should be tolerable for now.
474 var = key.split('.')[0]
476 return self.substitute(s, lv)
478 def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
479 return conv(substitute(l, lvars))
480 return list(map(func, s))
483 s = s(target=lvars['TARGETS'],
484 source=lvars['SOURCES'],
486 for_signature=(self.mode != SUBST_CMD))
488 # This probably indicates that it's a callable
489 # object that doesn't match our calling arguments
491 if self.mode == SUBST_RAW:
494 return self.substitute(s, lvars)
500 def substitute(self, args, lvars):
501 """Substitute expansions in an argument or list of arguments.
503 This serves as a wrapper for splitting up a string into
506 if is_String(args) and not isinstance(args, CmdStringHolder):
507 args = str(args) # In case it's a UserString.
509 def sub_match(match, conv=self.conv, expand=self.expand, lvars=lvars):
510 return conv(expand(match.group(1), lvars))
511 result = _dollar_exps.sub(sub_match, args)
513 # If the internal conversion routine doesn't return
514 # strings (it could be overridden to return Nodes, for
515 # example), then the 1.5.2 re module will throw this
516 # exception. Back off to a slower, general-purpose
517 # algorithm that works for all data types.
518 args = _separate_args.findall(args)
521 result.append(self.conv(self.expand(a, lvars)))
525 result = ''.join(map(str, result))
528 return self.expand(args, lvars)
531 conv = _strconv[mode]
533 # Doing this every time is a bit of a waste, since the Executor
534 # has typically already populated the OverrideEnvironment with
535 # $TARGET/$SOURCE variables. We're keeping this (for now), though,
536 # because it supports existing behavior that allows us to call
537 # an Action directly with an arbitrary target+source pair, which
538 # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
539 # If we dropped that behavior (or found another way to cover it),
540 # we could get rid of this call completely and just rely on the
541 # Executor setting the variables.
542 if 'TARGET' not in lvars:
543 d = subst_dict(target, source)
548 # We're (most likely) going to eval() things. If Python doesn't
549 # find a __builtins__ value in the global dictionary used for eval(),
550 # it copies the current global values for you. Avoid this by
551 # setting it explicitly and then deleting, so we don't pollute the
552 # construction environment Dictionary(ies) that are typically used
554 gvars['__builtins__'] = __builtins__
556 ss = StringSubber(env, mode, conv, gvars)
557 result = ss.substitute(strSubst, lvars)
560 del gvars['__builtins__']
564 if is_String(result):
565 # Remove $(-$) pairs and any stuff in between,
566 # if that's appropriate.
567 remove = _regex_remove[mode]
569 result = remove.sub('', result)
570 if mode != SUBST_RAW:
571 # Compress strings of white space characters into
573 result = _space_sep.sub(' ', result).strip()
574 elif is_Sequence(result):
575 remove = _list_remove[mode]
577 result = remove(result)
581 #Subst_List_Strings = {}
583 def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
584 """Substitute construction variables in a string (or list or other
585 object) and separate the arguments into a command list.
587 The companion scons_subst() function (above) handles basic
588 substitutions within strings, so see that function instead
589 if that's what you're looking for.
592 # Subst_List_Strings[strSubst] = Subst_List_Strings[strSubst] + 1
594 # Subst_List_Strings[strSubst] = 1
596 # SCons.Debug.caller_trace(1)
597 class ListSubber(UserList.UserList):
598 """A class to construct the results of a scons_subst_list() call.
600 Like StringSubber, this class binds a specific construction
601 environment, mode, target and source with two methods
602 (substitute() and expand()) that handle the expansion.
604 In addition, however, this class is used to track the state of
605 the result(s) we're gathering so we can do the appropriate thing
606 whenever we have to append another word to the result--start a new
607 line, start a new word, append to the current word, etc. We do
608 this by setting the "append" attribute to the right method so
609 that our wrapper methods only need ever call ListSubber.append(),
610 and the rest of the object takes care of doing the right thing
613 def __init__(self, env, mode, conv, gvars):
614 UserList.UserList.__init__(self, [])
620 if self.mode == SUBST_RAW:
621 self.add_strip = lambda x: self.append(x)
623 self.add_strip = lambda x: None
627 def expand(self, s, lvars, within_list):
628 """Expand a single "token" as necessary, appending the
629 expansion to the current result.
631 This handles expanding different types of things (strings,
632 lists, callables) appropriately. It calls the wrapper
633 substitute() method to re-expand things as necessary, so that
634 the results of expansions of side-by-side strings still get
635 re-evaluated separately, not smushed together.
641 except (IndexError, ValueError):
650 self.open_strip('$(')
652 self.close_strip('$)')
655 if key[0] == '{' or key.find('.') >= 0:
659 s = eval(key, self.gvars, lvars)
660 except KeyboardInterrupt:
663 if e.__class__ in AllowableExceptions:
665 raise_exception(e, lvars['TARGETS'], s)
669 elif key in self.gvars:
671 elif not NameError in AllowableExceptions:
672 raise_exception(NameError(), lvars['TARGETS'], s)
676 # Before re-expanding the result, handle
677 # recursive expansion by copying the local
678 # variable dictionary and overwriting a null
679 # string for the value of the variable name
682 var = key.split('.')[0]
684 self.substitute(s, lv, 0)
688 self.substitute(a, lvars, 1)
692 s = s(target=lvars['TARGETS'],
693 source=lvars['SOURCES'],
695 for_signature=(self.mode != SUBST_CMD))
697 # This probably indicates that it's a callable
698 # object that doesn't match our calling arguments
700 if self.mode == SUBST_RAW:
704 self.substitute(s, lvars, within_list)
710 def substitute(self, args, lvars, within_list):
711 """Substitute expansions in an argument or list of arguments.
713 This serves as a wrapper for splitting up a string into
717 if is_String(args) and not isinstance(args, CmdStringHolder):
718 args = str(args) # In case it's a UserString.
719 args = _separate_args.findall(args)
721 if a[0] in ' \t\n\r\f\v':
729 self.expand(a, lvars, within_list)
731 self.expand(args, lvars, within_list)
734 """Arrange for the next word to start a new line. This
735 is like starting a new word, except that we have to append
736 another line to the result."""
737 UserList.UserList.append(self, [])
741 """Arrange for the next word to append to the end of the
742 current last word in the result."""
743 self.append = self.add_to_current_word
746 """Arrange for the next word to start a new word."""
747 self.append = self.add_new_word
749 def add_to_current_word(self, x):
750 """Append the string x to the end of the current last word
751 in the result. If that is not possible, then just add
752 it as a new word. Make sure the entire concatenated string
753 inherits the object attributes of x (in particular, the
754 escape function) by wrapping it as CmdStringHolder."""
756 if not self.in_strip or self.mode != SUBST_SIG:
758 current_word = self[-1][-1]
762 # All right, this is a hack and it should probably
763 # be refactored out of existence in the future.
764 # The issue is that we want to smoosh words together
765 # and make one file name that gets escaped if
766 # we're expanding something like foo$EXTENSION,
767 # but we don't want to smoosh them together if
768 # it's something like >$TARGET, because then we'll
769 # treat the '>' like it's part of the file name.
770 # So for now, just hard-code looking for the special
771 # command-line redirection characters...
773 last_char = str(current_word)[-1]
776 if last_char in '<>|':
781 # We used to treat a word appended to a literal
782 # as a literal itself, but this caused problems
783 # with interpreting quotes around space-separated
784 # targets on command lines. Removing this makes
785 # none of the "substantive" end-to-end tests fail,
786 # so we'll take this out but leave it commented
787 # for now in case there's a problem not covered
788 # by the test cases and we need to resurrect this.
789 #literal1 = self.literal(self[-1][-1])
790 #literal2 = self.literal(x)
793 #y = CmdStringHolder(y, literal1 or literal2)
794 y = CmdStringHolder(y, None)
797 def add_new_word(self, x):
798 if not self.in_strip or self.mode != SUBST_SIG:
799 literal = self.literal(x)
802 x = CmdStringHolder(x, literal)
804 self.append = self.add_to_current_word
806 def literal(self, x):
809 except AttributeError:
814 def open_strip(self, x):
815 """Handle the "open strip" $( token."""
819 def close_strip(self, x):
820 """Handle the "close strip" $) token."""
825 conv = _strconv[mode]
827 # Doing this every time is a bit of a waste, since the Executor
828 # has typically already populated the OverrideEnvironment with
829 # $TARGET/$SOURCE variables. We're keeping this (for now), though,
830 # because it supports existing behavior that allows us to call
831 # an Action directly with an arbitrary target+source pair, which
832 # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
833 # If we dropped that behavior (or found another way to cover it),
834 # we could get rid of this call completely and just rely on the
835 # Executor setting the variables.
836 if 'TARGET' not in lvars:
837 d = subst_dict(target, source)
842 # We're (most likely) going to eval() things. If Python doesn't
843 # find a __builtins__ value in the global dictionary used for eval(),
844 # it copies the current global values for you. Avoid this by
845 # setting it explicitly and then deleting, so we don't pollute the
846 # construction environment Dictionary(ies) that are typically used
848 gvars['__builtins__'] = __builtins__
850 ls = ListSubber(env, mode, conv, gvars)
851 ls.substitute(strSubst, lvars, 0)
854 del gvars['__builtins__']
860 def scons_subst_once(strSubst, env, key):
861 """Perform single (non-recursive) substitution of a single
862 construction variable keyword.
864 This is used when setting a variable when copying or overriding values
865 in an Environment. We want to capture (expand) the old value before
866 we override it, so people can do things like:
868 env2 = env.Clone(CCFLAGS = '$CCFLAGS -g')
870 We do this with some straightforward, brute-force code here...
872 if isinstance(strSubst, str) and strSubst.find('$') < 0:
875 matchlist = ['$' + key, '${' + key + '}']
876 val = env.get(key, '')
877 def sub_match(match, val=val, matchlist=matchlist):
882 return ' '.join(map(str, a))
886 if is_Sequence(strSubst):
897 result.append(_dollar_exps.sub(sub_match, arg))
901 elif is_String(strSubst):
902 return _dollar_exps.sub(sub_match, strSubst)
908 # indent-tabs-mode:nil
910 # vim: set expandtab tabstop=4 shiftwidth=4: