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__"
40 from SCons.Util import is_String, is_Sequence
42 # Indexed by the SUBST_* constants below.
43 _strconv = [SCons.Util.to_String_for_subst,
44 SCons.Util.to_String_for_subst,
45 SCons.Util.to_String_for_signature]
49 AllowableExceptions = (IndexError, NameError)
51 def SetAllowableExceptions(*excepts):
52 global AllowableExceptions
53 AllowableExceptions = [_f for _f in excepts if _f]
55 def raise_exception(exception, target, s):
56 name = exception.__class__.__name__
57 msg = "%s `%s' trying to evaluate `%s'" % (name, exception, s)
59 raise SCons.Errors.BuildError, (target[0], msg)
61 raise SCons.Errors.UserError, msg
66 """A wrapper for a string. If you use this object wrapped
67 around a string, then it will be interpreted as literal.
68 When passed to the command interpreter, all special
69 characters will be escaped."""
70 def __init__(self, lstr):
76 def escape(self, escape_func):
77 return escape_func(self.lstr)
79 def for_signature(self):
85 class SpecialAttrWrapper:
86 """This is a wrapper for what we call a 'Node special attribute.'
87 This is any of the attributes of a Node that we can reference from
88 Environment variable substitution, such as $TARGET.abspath or
89 $SOURCES[1].filebase. We implement the same methods as Literal
90 so we can handle special characters, plus a for_signature method,
91 such that we can return some canonical string during signature
92 calculation to avoid unnecessary rebuilds."""
94 def __init__(self, lstr, for_signature=None):
95 """The for_signature parameter, if supplied, will be the
96 canonical string we return from for_signature(). Else
97 we will simply return lstr."""
100 self.forsig = for_signature
107 def escape(self, escape_func):
108 return escape_func(self.lstr)
110 def for_signature(self):
113 def is_literal(self):
116 def quote_spaces(arg):
117 """Generic function for putting double quotes around any string that
118 has white space in it."""
119 if ' ' in arg or '\t' in arg:
124 class CmdStringHolder(UserString.UserString):
125 """This is a special class used to hold strings generated by
126 scons_subst() and scons_subst_list(). It defines a special method
127 escape(). When passed a function with an escape algorithm for a
128 particular platform, it will return the contained string with the
129 proper escape sequences inserted.
131 def __init__(self, cmd, literal=None):
132 UserString.UserString.__init__(self, cmd)
133 self.literal = literal
135 def is_literal(self):
138 def escape(self, escape_func, quote_func=quote_spaces):
139 """Escape the string with the supplied function. The
140 function is expected to take an arbitrary string, then
141 return it with all special characters escaped and ready
142 for passing to the command interpreter.
144 After calling this function, the next call to str() will
145 return the escaped string.
148 if self.is_literal():
149 return escape_func(self.data)
150 elif ' ' in self.data or '\t' in self.data:
151 return quote_func(self.data)
155 def escape_list(mylist, escape_func):
156 """Escape a list of arguments by running the specified escape_func
157 on every object in the list that has an escape() method."""
158 def escape(obj, escape_func=escape_func):
161 except AttributeError:
164 return e(escape_func)
165 return list(map(escape, mylist))
168 """A wrapper class that delays turning a list of sources or targets
169 into a NodeList until it's needed. The specified function supplied
170 when the object is initialized is responsible for turning raw nodes
171 into proxies that implement the special attributes like .abspath,
172 .source, etc. This way, we avoid creating those proxies just
173 "in case" someone is going to use $TARGET or the like, and only
174 go through the trouble if we really have to.
176 In practice, this might be a wash performance-wise, but it's a little
177 cleaner conceptually...
180 def __init__(self, list, func):
183 def _return_nodelist(self):
185 def _gen_nodelist(self):
189 elif not is_Sequence(mylist):
191 # The map(self.func) call is what actually turns
192 # a list into appropriate proxies.
193 self.nodelist = SCons.Util.NodeList(list(map(self.func, mylist)))
194 self._create_nodelist = self._return_nodelist
196 _create_nodelist = _gen_nodelist
199 class Targets_or_Sources(UserList.UserList):
200 """A class that implements $TARGETS or $SOURCES expansions by in turn
201 wrapping a NLWrapper. This class handles the different methods used
202 to access the list, calling the NLWrapper to create proxies on demand.
204 Note that we subclass UserList.UserList purely so that the
205 is_Sequence() function will identify an object of this class as
206 a list during variable expansion. We're not really using any
207 UserList.UserList methods in practice.
209 def __init__(self, nl):
211 def __getattr__(self, attr):
212 nl = self.nl._create_nodelist()
213 return getattr(nl, attr)
214 def __getitem__(self, i):
215 nl = self.nl._create_nodelist()
217 def __getslice__(self, i, j):
218 nl = self.nl._create_nodelist()
219 i = max(i, 0); j = max(j, 0)
222 nl = self.nl._create_nodelist()
225 nl = self.nl._create_nodelist()
228 class Target_or_Source:
229 """A class that implements $TARGET or $SOURCE expansions by in turn
230 wrapping a NLWrapper. This class handles the different methods used
231 to access an individual proxy Node, calling the NLWrapper to create
234 def __init__(self, nl):
236 def __getattr__(self, attr):
237 nl = self.nl._create_nodelist()
241 # If there is nothing in the list, then we have no attributes to
242 # pass through, so raise AttributeError for everything.
243 raise AttributeError, "NodeList has no attribute: %s" % attr
244 return getattr(nl0, attr)
246 nl = self.nl._create_nodelist()
251 nl = self.nl._create_nodelist()
256 class NullNodeList(SCons.Util.NullSeq):
257 def __call__(self, *args, **kwargs): return ''
258 def __str__(self): return ''
259 # TODO(1.5): unneeded after new-style classes introduce iterators
260 def __getitem__(self, i):
263 NullNodesList = NullNodeList()
265 def subst_dict(target, source):
266 """Create a dictionary for substitution of special
267 construction variables.
269 This translates the following special arguments:
271 target - the target (object or array of objects),
272 used to generate the TARGET and TARGETS
273 construction variables
275 source - the source (object or array of objects),
276 used to generate the SOURCES and SOURCE
277 construction variables
282 def get_tgt_subst_proxy(thing):
284 subst_proxy = thing.get_subst_proxy()
285 except AttributeError:
286 subst_proxy = thing # probably a string, just return it
288 tnl = NLWrapper(target, get_tgt_subst_proxy)
289 dict['TARGETS'] = Targets_or_Sources(tnl)
290 dict['TARGET'] = Target_or_Source(tnl)
292 # This is a total cheat, but hopefully this dictionary goes
293 # away soon anyway. We just let these expand to $TARGETS
294 # because that's "good enough" for the use of ToolSurrogates
295 # (see test/ToolSurrogate.py) to generate documentation.
296 dict['CHANGED_TARGETS'] = '$TARGETS'
297 dict['UNCHANGED_TARGETS'] = '$TARGETS'
299 dict['TARGETS'] = NullNodesList
300 dict['TARGET'] = NullNodesList
303 def get_src_subst_proxy(node):
306 except AttributeError:
311 return node.get_subst_proxy()
312 except AttributeError:
313 return node # probably a String, just return it
314 snl = NLWrapper(source, get_src_subst_proxy)
315 dict['SOURCES'] = Targets_or_Sources(snl)
316 dict['SOURCE'] = Target_or_Source(snl)
318 # This is a total cheat, but hopefully this dictionary goes
319 # away soon anyway. We just let these expand to $TARGETS
320 # because that's "good enough" for the use of ToolSurrogates
321 # (see test/ToolSurrogate.py) to generate documentation.
322 dict['CHANGED_SOURCES'] = '$SOURCES'
323 dict['UNCHANGED_SOURCES'] = '$SOURCES'
325 dict['SOURCES'] = NullNodesList
326 dict['SOURCE'] = NullNodesList
330 # Constants for the "mode" parameter to scons_subst_list() and
331 # scons_subst(). SUBST_RAW gives the raw command line. SUBST_CMD
332 # gives a command line suitable for passing to a shell. SUBST_SIG
333 # gives a command line appropriate for calculating the signature
334 # of a command line...if this changes, we should rebuild.
339 _rm = re.compile(r'\$[()]')
340 _remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)')
342 # Indexed by the SUBST_* constants above.
343 _regex_remove = [ _rm, None, _remove ]
346 #return [ l for l in list if not l in ('$(', '$)') ]
347 return [l for l in list if not l in ('$(', '$)')]
349 def _remove_list(list):
351 do_append = result.append
354 do_append = lambda x: None
356 do_append = result.append
361 # Indexed by the SUBST_* constants above.
362 _list_remove = [ _rm_list, None, _remove_list ]
364 # Regular expressions for splitting strings and handling substitutions,
365 # for use by the scons_subst() and scons_subst_list() functions:
367 # The first expression compiled matches all of the $-introduced tokens
368 # that we need to process in some way, and is used for substitutions.
369 # The expressions it matches are:
374 # "$variable" [must begin with alphabetic or underscore]
377 # The second expression compiled is used for splitting strings into tokens
378 # to be processed, and it matches all of the tokens listed above, plus
379 # the following that affect how arguments do or don't get joined together:
382 # "non-white-space" [without any dollar signs]
383 # "$" [single dollar sign]
385 _dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}'
386 _dollar_exps = re.compile(r'(%s)' % _dollar_exps_str)
387 _separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str)
389 # This regular expression is used to replace strings of multiple white
390 # space characters in the string result from the scons_subst() function.
391 _space_sep = re.compile(r'[\t ]+(?![^{]*})')
393 def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
394 """Expand a string or list containing construction variable
397 This is the work-horse function for substitutions in file names
398 and the like. The companion scons_subst_list() function (below)
399 handles separating command lines into lists of arguments, so see
400 that function if that's what you're looking for.
402 if type(strSubst) == types.StringType and strSubst.find('$') < 0:
406 """A class to construct the results of a scons_subst() call.
408 This binds a specific construction environment, mode, target and
409 source with two methods (substitute() and expand()) that handle
412 def __init__(self, env, mode, conv, gvars):
418 def expand(self, s, lvars):
419 """Expand a single "token" as necessary, returning an
420 appropriate string containing the expansion.
422 This handles expanding different types of things (strings,
423 lists, callables) appropriately. It calls the wrapper
424 substitute() method to re-expand things as necessary, so that
425 the results of expansions of side-by-side strings still get
426 re-evaluated separately, not smushed together.
431 except (IndexError, ValueError):
441 if key[0] == '{' or key.find('.') >= 0:
445 s = eval(key, self.gvars, lvars)
446 except KeyboardInterrupt:
449 if e.__class__ in AllowableExceptions:
451 raise_exception(e, lvars['TARGETS'], s)
455 elif key in self.gvars:
457 elif not NameError in AllowableExceptions:
458 raise_exception(NameError(key), lvars['TARGETS'], s)
462 # Before re-expanding the result, handle
463 # recursive expansion by copying the local
464 # variable dictionary and overwriting a null
465 # string for the value of the variable name
468 # This could potentially be optimized by only
469 # copying lvars when s contains more expansions,
470 # but lvars is usually supposed to be pretty
471 # small, and deeply nested variable expansions
472 # are probably more the exception than the norm,
473 # so it should be tolerable for now.
475 var = key.split('.')[0]
477 return self.substitute(s, lv)
479 def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
480 return conv(substitute(l, lvars))
481 return list(map(func, s))
484 s = s(target=lvars['TARGETS'],
485 source=lvars['SOURCES'],
487 for_signature=(self.mode != SUBST_CMD))
489 # This probably indicates that it's a callable
490 # object that doesn't match our calling arguments
492 if self.mode == SUBST_RAW:
495 return self.substitute(s, lvars)
501 def substitute(self, args, lvars):
502 """Substitute expansions in an argument or list of arguments.
504 This serves as a wrapper for splitting up a string into
507 if is_String(args) and not isinstance(args, CmdStringHolder):
508 args = str(args) # In case it's a UserString.
510 def sub_match(match, conv=self.conv, expand=self.expand, lvars=lvars):
511 return conv(expand(match.group(1), lvars))
512 result = _dollar_exps.sub(sub_match, args)
514 # If the internal conversion routine doesn't return
515 # strings (it could be overridden to return Nodes, for
516 # example), then the 1.5.2 re module will throw this
517 # exception. Back off to a slower, general-purpose
518 # algorithm that works for all data types.
519 args = _separate_args.findall(args)
522 result.append(self.conv(self.expand(a, lvars)))
526 result = ''.join(map(str, result))
529 return self.expand(args, lvars)
532 conv = _strconv[mode]
534 # Doing this every time is a bit of a waste, since the Executor
535 # has typically already populated the OverrideEnvironment with
536 # $TARGET/$SOURCE variables. We're keeping this (for now), though,
537 # because it supports existing behavior that allows us to call
538 # an Action directly with an arbitrary target+source pair, which
539 # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
540 # If we dropped that behavior (or found another way to cover it),
541 # we could get rid of this call completely and just rely on the
542 # Executor setting the variables.
543 if 'TARGET' not in lvars:
544 d = subst_dict(target, source)
549 # We're (most likely) going to eval() things. If Python doesn't
550 # find a __builtins__ value in the global dictionary used for eval(),
551 # it copies the current global values for you. Avoid this by
552 # setting it explicitly and then deleting, so we don't pollute the
553 # construction environment Dictionary(ies) that are typically used
555 gvars['__builtins__'] = __builtins__
557 ss = StringSubber(env, mode, conv, gvars)
558 result = ss.substitute(strSubst, lvars)
561 del gvars['__builtins__']
565 if is_String(result):
566 # Remove $(-$) pairs and any stuff in between,
567 # if that's appropriate.
568 remove = _regex_remove[mode]
570 result = remove.sub('', result)
571 if mode != SUBST_RAW:
572 # Compress strings of white space characters into
574 result = _space_sep.sub(' ', result).strip()
575 elif is_Sequence(result):
576 remove = _list_remove[mode]
578 result = remove(result)
582 #Subst_List_Strings = {}
584 def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
585 """Substitute construction variables in a string (or list or other
586 object) and separate the arguments into a command list.
588 The companion scons_subst() function (above) handles basic
589 substitutions within strings, so see that function instead
590 if that's what you're looking for.
593 # Subst_List_Strings[strSubst] = Subst_List_Strings[strSubst] + 1
595 # Subst_List_Strings[strSubst] = 1
597 # SCons.Debug.caller_trace(1)
598 class ListSubber(UserList.UserList):
599 """A class to construct the results of a scons_subst_list() call.
601 Like StringSubber, this class binds a specific construction
602 environment, mode, target and source with two methods
603 (substitute() and expand()) that handle the expansion.
605 In addition, however, this class is used to track the state of
606 the result(s) we're gathering so we can do the appropriate thing
607 whenever we have to append another word to the result--start a new
608 line, start a new word, append to the current word, etc. We do
609 this by setting the "append" attribute to the right method so
610 that our wrapper methods only need ever call ListSubber.append(),
611 and the rest of the object takes care of doing the right thing
614 def __init__(self, env, mode, conv, gvars):
615 UserList.UserList.__init__(self, [])
621 if self.mode == SUBST_RAW:
622 self.add_strip = lambda x: self.append(x)
624 self.add_strip = lambda x: None
628 def expand(self, s, lvars, within_list):
629 """Expand a single "token" as necessary, appending the
630 expansion to the current result.
632 This handles expanding different types of things (strings,
633 lists, callables) appropriately. It calls the wrapper
634 substitute() method to re-expand things as necessary, so that
635 the results of expansions of side-by-side strings still get
636 re-evaluated separately, not smushed together.
642 except (IndexError, ValueError):
651 self.open_strip('$(')
653 self.close_strip('$)')
656 if key[0] == '{' or key.find('.') >= 0:
660 s = eval(key, self.gvars, lvars)
661 except KeyboardInterrupt:
664 if e.__class__ in AllowableExceptions:
666 raise_exception(e, lvars['TARGETS'], s)
670 elif key in self.gvars:
672 elif not NameError in AllowableExceptions:
673 raise_exception(NameError(), lvars['TARGETS'], s)
677 # Before re-expanding the result, handle
678 # recursive expansion by copying the local
679 # variable dictionary and overwriting a null
680 # string for the value of the variable name
683 var = key.split('.')[0]
685 self.substitute(s, lv, 0)
689 self.substitute(a, lvars, 1)
693 s = s(target=lvars['TARGETS'],
694 source=lvars['SOURCES'],
696 for_signature=(self.mode != SUBST_CMD))
698 # This probably indicates that it's a callable
699 # object that doesn't match our calling arguments
701 if self.mode == SUBST_RAW:
705 self.substitute(s, lvars, within_list)
711 def substitute(self, args, lvars, within_list):
712 """Substitute expansions in an argument or list of arguments.
714 This serves as a wrapper for splitting up a string into
718 if is_String(args) and not isinstance(args, CmdStringHolder):
719 args = str(args) # In case it's a UserString.
720 args = _separate_args.findall(args)
722 if a[0] in ' \t\n\r\f\v':
730 self.expand(a, lvars, within_list)
732 self.expand(args, lvars, within_list)
735 """Arrange for the next word to start a new line. This
736 is like starting a new word, except that we have to append
737 another line to the result."""
738 UserList.UserList.append(self, [])
742 """Arrange for the next word to append to the end of the
743 current last word in the result."""
744 self.append = self.add_to_current_word
747 """Arrange for the next word to start a new word."""
748 self.append = self.add_new_word
750 def add_to_current_word(self, x):
751 """Append the string x to the end of the current last word
752 in the result. If that is not possible, then just add
753 it as a new word. Make sure the entire concatenated string
754 inherits the object attributes of x (in particular, the
755 escape function) by wrapping it as CmdStringHolder."""
757 if not self.in_strip or self.mode != SUBST_SIG:
759 current_word = self[-1][-1]
763 # All right, this is a hack and it should probably
764 # be refactored out of existence in the future.
765 # The issue is that we want to smoosh words together
766 # and make one file name that gets escaped if
767 # we're expanding something like foo$EXTENSION,
768 # but we don't want to smoosh them together if
769 # it's something like >$TARGET, because then we'll
770 # treat the '>' like it's part of the file name.
771 # So for now, just hard-code looking for the special
772 # command-line redirection characters...
774 last_char = str(current_word)[-1]
777 if last_char in '<>|':
782 # We used to treat a word appended to a literal
783 # as a literal itself, but this caused problems
784 # with interpreting quotes around space-separated
785 # targets on command lines. Removing this makes
786 # none of the "substantive" end-to-end tests fail,
787 # so we'll take this out but leave it commented
788 # for now in case there's a problem not covered
789 # by the test cases and we need to resurrect this.
790 #literal1 = self.literal(self[-1][-1])
791 #literal2 = self.literal(x)
794 #y = CmdStringHolder(y, literal1 or literal2)
795 y = CmdStringHolder(y, None)
798 def add_new_word(self, x):
799 if not self.in_strip or self.mode != SUBST_SIG:
800 literal = self.literal(x)
803 x = CmdStringHolder(x, literal)
805 self.append = self.add_to_current_word
807 def literal(self, x):
810 except AttributeError:
815 def open_strip(self, x):
816 """Handle the "open strip" $( token."""
820 def close_strip(self, x):
821 """Handle the "close strip" $) token."""
826 conv = _strconv[mode]
828 # Doing this every time is a bit of a waste, since the Executor
829 # has typically already populated the OverrideEnvironment with
830 # $TARGET/$SOURCE variables. We're keeping this (for now), though,
831 # because it supports existing behavior that allows us to call
832 # an Action directly with an arbitrary target+source pair, which
833 # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
834 # If we dropped that behavior (or found another way to cover it),
835 # we could get rid of this call completely and just rely on the
836 # Executor setting the variables.
837 if 'TARGET' not in lvars:
838 d = subst_dict(target, source)
843 # We're (most likely) going to eval() things. If Python doesn't
844 # find a __builtins__ value in the global dictionary used for eval(),
845 # it copies the current global values for you. Avoid this by
846 # setting it explicitly and then deleting, so we don't pollute the
847 # construction environment Dictionary(ies) that are typically used
849 gvars['__builtins__'] = __builtins__
851 ls = ListSubber(env, mode, conv, gvars)
852 ls.substitute(strSubst, lvars, 0)
855 del gvars['__builtins__']
861 def scons_subst_once(strSubst, env, key):
862 """Perform single (non-recursive) substitution of a single
863 construction variable keyword.
865 This is used when setting a variable when copying or overriding values
866 in an Environment. We want to capture (expand) the old value before
867 we override it, so people can do things like:
869 env2 = env.Clone(CCFLAGS = '$CCFLAGS -g')
871 We do this with some straightforward, brute-force code here...
873 if type(strSubst) == types.StringType and strSubst.find('$') < 0:
876 matchlist = ['$' + key, '${' + key + '}']
877 val = env.get(key, '')
878 def sub_match(match, val=val, matchlist=matchlist):
883 return ' '.join(map(str, a))
887 if is_Sequence(strSubst):
898 result.append(_dollar_exps.sub(sub_match, arg))
902 elif is_String(strSubst):
903 return _dollar_exps.sub(sub_match, strSubst)
909 # indent-tabs-mode:nil
911 # vim: set expandtab tabstop=4 shiftwidth=4: