3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 """SCons string substitution."""
28 from inspect
import signature
, Parameter
29 from typing
import Optional
32 from SCons
.Util
import is_String
, is_Sequence
34 # Indexed by the SUBST_* constants below.
36 SCons
.Util
.to_String_for_subst
,
37 SCons
.Util
.to_String_for_subst
,
38 SCons
.Util
.to_String_for_signature
,
41 AllowableExceptions
= (IndexError, NameError)
44 def SetAllowableExceptions(*excepts
) -> None:
45 global AllowableExceptions
46 AllowableExceptions
= [_f
for _f
in excepts
if _f
]
49 def raise_exception(exception
, target
, s
):
50 name
= exception
.__class
__.__name
__
51 msg
= "%s `%s' trying to evaluate `%s'" % (name
, exception
, s
)
53 raise SCons
.Errors
.BuildError(target
[0], msg
)
55 raise SCons
.Errors
.UserError(msg
)
59 """A wrapper for a string. If you use this object wrapped
60 around a string, then it will be interpreted as literal.
61 When passed to the command interpreter, all special
62 characters will be escaped."""
63 def __init__(self
, lstr
) -> None:
66 def __str__(self
) -> str:
69 def escape(self
, escape_func
):
70 return escape_func(self
.lstr
)
72 def for_signature(self
):
75 def is_literal(self
) -> bool:
78 def __eq__(self
, other
):
79 if not isinstance(other
, Literal
):
81 return self
.lstr
== other
.lstr
83 def __neq__(self
, other
) -> bool:
84 return not self
.__eq
__(other
)
87 return hash(self
.lstr
)
89 class SpecialAttrWrapper
:
90 """This is a wrapper for what we call a 'Node special attribute.'
91 This is any of the attributes of a Node that we can reference from
92 Environment variable substitution, such as $TARGET.abspath or
93 $SOURCES[1].filebase. We implement the same methods as Literal
94 so we can handle special characters, plus a for_signature method,
95 such that we can return some canonical string during signature
96 calculation to avoid unnecessary rebuilds."""
98 def __init__(self
, lstr
, for_signature
=None) -> None:
99 """The for_signature parameter, if supplied, will be the
100 canonical string we return from for_signature(). Else
101 we will simply return lstr."""
104 self
.forsig
= for_signature
108 def __str__(self
) -> str:
111 def escape(self
, escape_func
):
112 return escape_func(self
.lstr
)
114 def for_signature(self
):
117 def is_literal(self
) -> bool:
120 def quote_spaces(arg
):
121 """Generic function for putting double quotes around any string that
122 has white space in it."""
123 if ' ' in arg
or '\t' in arg
:
128 class CmdStringHolder(collections
.UserString
):
129 """This is a special class used to hold strings generated by
130 scons_subst() and scons_subst_list(). It defines a special method
131 escape(). When passed a function with an escape algorithm for a
132 particular platform, it will return the contained string with the
133 proper escape sequences inserted.
135 def __init__(self
, cmd
, literal
=None) -> None:
136 super().__init
__(cmd
)
137 self
.literal
= literal
139 def is_literal(self
) -> bool:
142 def escape(self
, escape_func
, quote_func
=quote_spaces
):
143 """Escape the string with the supplied function. The
144 function is expected to take an arbitrary string, then
145 return it with all special characters escaped and ready
146 for passing to the command interpreter.
148 After calling this function, the next call to str() will
149 return the escaped string.
152 if self
.is_literal():
153 return escape_func(self
.data
)
154 elif ' ' in self
.data
or '\t' in self
.data
:
155 return quote_func(self
.data
)
159 def escape_list(mylist
, escape_func
):
160 """Escape a list of arguments by running the specified escape_func
161 on every object in the list that has an escape() method."""
162 def escape(obj
, escape_func
=escape_func
):
165 except AttributeError:
168 return e(escape_func
)
169 return list(map(escape
, mylist
))
172 """A wrapper class that delays turning a list of sources or targets
173 into a NodeList until it's needed. The specified function supplied
174 when the object is initialized is responsible for turning raw nodes
175 into proxies that implement the special attributes like .abspath,
176 .source, etc. This way, we avoid creating those proxies just
177 "in case" someone is going to use $TARGET or the like, and only
178 go through the trouble if we really have to.
180 In practice, this might be a wash performance-wise, but it's a little
181 cleaner conceptually...
184 def __init__(self
, list, func
) -> None:
187 def _return_nodelist(self
):
189 def _gen_nodelist(self
):
193 elif not is_Sequence(mylist
):
195 # The map(self.func) call is what actually turns
196 # a list into appropriate proxies.
197 self
.nodelist
= SCons
.Util
.NodeList(list(map(self
.func
, mylist
)))
198 self
._create
_nodelist
= self
._return
_nodelist
200 _create_nodelist
= _gen_nodelist
203 class Targets_or_Sources(collections
.UserList
):
204 """A class that implements $TARGETS or $SOURCES expansions by in turn
205 wrapping a NLWrapper. This class handles the different methods used
206 to access the list, calling the NLWrapper to create proxies on demand.
208 Note that we subclass collections.UserList purely so that the
209 is_Sequence() function will identify an object of this class as
210 a list during variable expansion. We're not really using any
211 collections.UserList methods in practice.
213 def __init__(self
, nl
) -> None:
215 def __getattr__(self
, attr
):
216 nl
= self
.nl
._create
_nodelist
()
217 return getattr(nl
, attr
)
218 def __getitem__(self
, i
):
219 nl
= self
.nl
._create
_nodelist
()
221 def __str__(self
) -> str:
222 nl
= self
.nl
._create
_nodelist
()
224 def __repr__(self
) -> str:
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
) -> None:
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
)
245 def __str__(self
) -> str:
246 nl
= self
.nl
._create
_nodelist
()
250 def __repr__(self
) -> str:
251 nl
= self
.nl
._create
_nodelist
()
256 class NullNodeList(SCons
.Util
.NullSeq
):
257 def __call__(self
, *args
, **kwargs
) -> str: return ''
258 def __str__(self
) -> str: return ''
260 NullNodesList
= NullNodeList()
262 def subst_dict(target
, source
):
263 """Create a dictionary for substitution of special
264 construction variables.
266 This translates the following special arguments:
268 target - the target (object or array of objects),
269 used to generate the TARGET and TARGETS
270 construction variables
272 source - the source (object or array of objects),
273 used to generate the SOURCES and SOURCE
274 construction variables
279 def get_tgt_subst_proxy(thing
):
281 subst_proxy
= thing
.get_subst_proxy()
282 except AttributeError:
283 subst_proxy
= thing
# probably a string, just return it
285 tnl
= NLWrapper(target
, get_tgt_subst_proxy
)
286 dict['TARGETS'] = Targets_or_Sources(tnl
)
287 dict['TARGET'] = Target_or_Source(tnl
)
289 # This is a total cheat, but hopefully this dictionary goes
290 # away soon anyway. We just let these expand to $TARGETS
291 # because that's "good enough" for the use of ToolSurrogates
292 # (see test/ToolSurrogate.py) to generate documentation.
293 dict['CHANGED_TARGETS'] = '$TARGETS'
294 dict['UNCHANGED_TARGETS'] = '$TARGETS'
296 dict['TARGETS'] = NullNodesList
297 dict['TARGET'] = NullNodesList
300 def get_src_subst_proxy(node
):
303 except AttributeError:
308 return node
.get_subst_proxy()
309 except AttributeError:
310 return node
# probably a String, just return it
311 snl
= NLWrapper(source
, get_src_subst_proxy
)
312 dict['SOURCES'] = Targets_or_Sources(snl
)
313 dict['SOURCE'] = Target_or_Source(snl
)
315 # This is a total cheat, but hopefully this dictionary goes
316 # away soon anyway. We just let these expand to $TARGETS
317 # because that's "good enough" for the use of ToolSurrogates
318 # (see test/ToolSurrogate.py) to generate documentation.
319 dict['CHANGED_SOURCES'] = '$SOURCES'
320 dict['UNCHANGED_SOURCES'] = '$SOURCES'
322 dict['SOURCES'] = NullNodesList
323 dict['SOURCE'] = NullNodesList
328 _callable_args_set
= {'target', 'source', 'env', 'for_signature'}
331 """A class to construct the results of a scons_subst() call.
333 This binds a specific construction environment, mode, target and
334 source with two methods (substitute() and expand()) that handle
339 def __init__(self
, env
, mode
, conv
, gvars
) -> None:
345 def expand(self
, s
, lvars
):
346 """Expand a single "token" as necessary, returning an
347 appropriate string containing the expansion.
349 This handles expanding different types of things (strings,
350 lists, callables) appropriately. It calls the wrapper
351 substitute() method to re-expand things as necessary, so that
352 the results of expansions of side-by-side strings still get
353 re-evaluated separately, not smushed together.
358 except (IndexError, ValueError):
363 # In this case keep the double $'s which we'll later
364 # swap for a single dollar sign as we need to retain
365 # this information to properly avoid matching "$("" when
366 # the actual text was "$$("" (or "$)"" when "$$)"" )
372 if key
[0] == '{' or '.' in key
:
376 # Store for error messages if we fail to expand the
382 elif key
in self
.gvars
:
386 s
= eval(key
, self
.gvars
, lvars
)
387 except KeyboardInterrupt:
389 except Exception as e
:
390 if e
.__class
__ in AllowableExceptions
:
392 raise_exception(e
, lvars
['TARGETS'], old_s
)
394 if s
is None and NameError not in AllowableExceptions
:
395 raise_exception(NameError(key
), lvars
['TARGETS'], old_s
)
399 # Before re-expanding the result, handle
400 # recursive expansion by copying the local
401 # variable dictionary and overwriting a null
402 # string for the value of the variable name
405 # This could potentially be optimized by only
406 # copying lvars when s contains more expansions,
407 # but lvars is usually supposed to be pretty
408 # small, and deeply nested variable expansions
409 # are probably more the exception than the norm,
410 # so it should be tolerable for now.
412 var
= key
.split('.')[0]
414 return self
.substitute(s
, lv
)
416 def func(l
, conv
=self
.conv
, substitute
=self
.substitute
, lvars
=lvars
):
417 return conv(substitute(l
, lvars
))
418 return list(map(func
, s
))
421 # SCons has the unusual Null class where any __getattr__ call returns it's self,
422 # which does not work the signature module, and the Null class returns an empty
423 # string if called on, so we make an exception in this condition for Null class
424 # Also allow callables where the only non default valued args match the expected defaults
425 # this should also allow functools.partial's to work.
426 if isinstance(s
, SCons
.Util
.Null
) or {k
for k
, v
in signature(s
).parameters
.items() if
427 k
in _callable_args_set
or v
.default
== Parameter
.empty
} == _callable_args_set
:
429 s
= s(target
=lvars
['TARGETS'],
430 source
=lvars
['SOURCES'],
432 for_signature
=(self
.mode
== SUBST_SIG
))
434 # This probably indicates that it's a callable
435 # object that doesn't match our calling arguments
437 if self
.mode
== SUBST_RAW
:
440 return self
.substitute(s
, lvars
)
446 def substitute(self
, args
, lvars
):
447 """Substitute expansions in an argument or list of arguments.
449 This serves as a wrapper for splitting up a string into
452 def sub_match(match
):
453 return self
.conv(self
.expand(match
.group(1), lvars
))
455 if is_String(args
) and not isinstance(args
, CmdStringHolder
):
456 args
= str(args
) # In case it's a UserString.
458 result
= _dollar_exps
.sub(sub_match
, args
)
460 # If the internal conversion routine doesn't return
461 # strings (it could be overridden to return Nodes, for
462 # example), then the 1.5.2 re module will throw this
463 # exception. Back off to a slower, general-purpose
464 # algorithm that works for all data types.
465 args
= _separate_args
.findall(args
)
468 result
.append(self
.conv(self
.expand(a
, lvars
)))
472 result
= ''.join(map(str, result
))
475 return self
.expand(args
, lvars
)
478 class ListSubber(collections
.UserList
):
479 """A class to construct the results of a scons_subst_list() call.
481 Like StringSubber, this class binds a specific construction
482 environment, mode, target and source with two methods
483 (substitute() and expand()) that handle the expansion.
485 In addition, however, this class is used to track the state of
486 the result(s) we're gathering so we can do the appropriate thing
487 whenever we have to append another word to the result--start a new
488 line, start a new word, append to the current word, etc. We do
489 this by setting the "append" attribute to the right method so
490 that our wrapper methods only need ever call ListSubber.append(),
491 and the rest of the object takes care of doing the right thing
494 def __init__(self
, env
, mode
, conv
, gvars
) -> None:
501 if self
.mode
== SUBST_RAW
:
502 self
.add_strip
= lambda x
: self
.append(x
)
504 self
.add_strip
= lambda x
: None
508 def expanded(self
, s
) -> bool:
509 """Determines if the string s requires further expansion.
511 Due to the implementation of ListSubber expand will call
512 itself 2 additional times for an already expanded string. This
513 method is used to determine if a string is already fully
514 expanded and if so exit the loop early to prevent these
517 if not is_String(s
) or isinstance(s
, CmdStringHolder
):
520 s
= str(s
) # in case it's a UserString
521 return _separate_args
.findall(s
) is None
523 def expand(self
, s
, lvars
, within_list
):
524 """Expand a single "token" as necessary, appending the
525 expansion to the current result.
527 This handles expanding different types of things (strings,
528 lists, callables) appropriately. It calls the wrapper
529 substitute() method to re-expand things as necessary, so that
530 the results of expansions of side-by-side strings still get
531 re-evaluated separately, not smushed together.
537 except (IndexError, ValueError):
546 self
.open_strip('$(')
548 self
.close_strip('$)')
551 if key
[0] == '{' or key
.find('.') >= 0:
555 # Store for error messages if we fail to expand the
561 elif key
in self
.gvars
:
565 s
= eval(key
, self
.gvars
, lvars
)
566 except KeyboardInterrupt:
568 except Exception as e
:
569 if e
.__class
__ in AllowableExceptions
:
571 raise_exception(e
, lvars
['TARGETS'], old_s
)
573 if s
is None and NameError not in AllowableExceptions
:
574 raise_exception(NameError(), lvars
['TARGETS'], old_s
)
578 # If the string is already full expanded there's no
579 # need to continue recursion.
584 # Before re-expanding the result, handle
585 # recursive expansion by copying the local
586 # variable dictionary and overwriting a null
587 # string for the value of the variable name
590 var
= key
.split('.')[0]
592 self
.substitute(s
, lv
, 0)
596 self
.substitute(a
, lvars
, 1)
599 # SCons has the unusual Null class where any __getattr__ call returns it's self,
600 # which does not work the signature module, and the Null class returns an empty
601 # string if called on, so we make an exception in this condition for Null class
602 # Also allow callables where the only non default valued args match the expected defaults
603 # this should also allow functools.partial's to work.
604 if isinstance(s
, SCons
.Util
.Null
) or {k
for k
, v
in signature(s
).parameters
.items() if
605 k
in _callable_args_set
or v
.default
== Parameter
.empty
} == _callable_args_set
:
607 s
= s(target
=lvars
['TARGETS'],
608 source
=lvars
['SOURCES'],
610 for_signature
=(self
.mode
!= SUBST_CMD
))
612 # This probably indicates that it's a callable
613 # object that doesn't match our calling arguments
615 if self
.mode
== SUBST_RAW
:
619 self
.substitute(s
, lvars
, within_list
)
625 def substitute(self
, args
, lvars
, within_list
) -> None:
626 """Substitute expansions in an argument or list of arguments.
628 This serves as a wrapper for splitting up a string into
632 if is_String(args
) and not isinstance(args
, CmdStringHolder
):
633 args
= str(args
) # In case it's a UserString.
634 args
= _separate_args
.findall(args
)
636 if a
[0] in ' \t\n\r\f\v':
644 self
.expand(a
, lvars
, within_list
)
646 self
.expand(args
, lvars
, within_list
)
648 def next_line(self
) -> None:
649 """Arrange for the next word to start a new line. This
650 is like starting a new word, except that we have to append
651 another line to the result."""
652 collections
.UserList
.append(self
, [])
655 def this_word(self
) -> None:
656 """Arrange for the next word to append to the end of the
657 current last word in the result."""
658 self
.append
= self
.add_to_current_word
660 def next_word(self
) -> None:
661 """Arrange for the next word to start a new word."""
662 self
.append
= self
.add_new_word
664 def add_to_current_word(self
, x
) -> None:
665 """Append the string x to the end of the current last word
666 in the result. If that is not possible, then just add
667 it as a new word. Make sure the entire concatenated string
668 inherits the object attributes of x (in particular, the
669 escape function) by wrapping it as CmdStringHolder."""
671 if not self
.in_strip
or self
.mode
!= SUBST_SIG
:
673 current_word
= self
[-1][-1]
677 # All right, this is a hack and it should probably
678 # be refactored out of existence in the future.
679 # The issue is that we want to smoosh words together
680 # and make one file name that gets escaped if
681 # we're expanding something like foo$EXTENSION,
682 # but we don't want to smoosh them together if
683 # it's something like >$TARGET, because then we'll
684 # treat the '>' like it's part of the file name.
685 # So for now, just hard-code looking for the special
686 # command-line redirection characters...
688 last_char
= str(current_word
)[-1]
691 if last_char
in '<>|':
696 # We used to treat a word appended to a literal
697 # as a literal itself, but this caused problems
698 # with interpreting quotes around space-separated
699 # targets on command lines. Removing this makes
700 # none of the "substantive" end-to-end tests fail,
701 # so we'll take this out but leave it commented
702 # for now in case there's a problem not covered
703 # by the test cases and we need to resurrect this.
704 #literal1 = self.literal(self[-1][-1])
705 #literal2 = self.literal(x)
708 #y = CmdStringHolder(y, literal1 or literal2)
709 y
= CmdStringHolder(y
, None)
712 def add_new_word(self
, x
) -> None:
713 if not self
.in_strip
or self
.mode
!= SUBST_SIG
:
714 literal
= self
.literal(x
)
717 x
= CmdStringHolder(x
, literal
)
719 self
.append
= self
.add_to_current_word
721 def literal(self
, x
):
724 except AttributeError:
729 def open_strip(self
, x
) -> None:
730 """Handle the "open strip" $( token."""
734 def close_strip(self
, x
) -> None:
735 """Handle the "close strip" $) token."""
740 # Constants for the "mode" parameter to scons_subst_list() and
741 # scons_subst(). SUBST_RAW gives the raw command line. SUBST_CMD
742 # gives a command line suitable for passing to a shell. SUBST_SIG
743 # gives a command line appropriate for calculating the signature
744 # of a command line...if this changes, we should rebuild.
749 _rm
= re
.compile(r
'\$[()]')
751 # Note the pattern below only matches $( or $) when there is no
752 # preceeding $. (Thus the (?<!\$))
753 _rm_split
= re
.compile(r
'(?<!\$)(\$[()])')
755 # Indexed by the SUBST_* constants above.
756 _regex_remove
= [ _rm
, None, _rm_split
]
759 return [l
for l
in list if l
not in ('$(', '$)')]
761 def _remove_list(list):
777 # Indexed by the SUBST_* constants above.
778 _list_remove
= [ _rm_list
, None, _remove_list
]
780 # Regular expressions for splitting strings and handling substitutions,
781 # for use by the scons_subst() and scons_subst_list() functions:
783 # The first expression compiled matches all of the $-introduced tokens
784 # that we need to process in some way, and is used for substitutions.
785 # The expressions it matches are:
790 # "$variable" [must begin with alphabetic or underscore]
793 # The second expression compiled is used for splitting strings into tokens
794 # to be processed, and it matches all of the tokens listed above, plus
795 # the following that affect how arguments do or don't get joined together:
798 # "non-white-space" [without any dollar signs]
799 # "$" [single dollar sign]
801 _dollar_exps_str
= r
'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}'
802 _dollar_exps
= re
.compile(r
'(%s)' % _dollar_exps_str
)
803 _separate_args
= re
.compile(r
'(%s|\s+|[^\s$]+|\$)' % _dollar_exps_str
)
805 # This regular expression is used to replace strings of multiple white
806 # space characters in the string result from the scons_subst() function.
807 _space_sep
= re
.compile(r
'[\t ]+(?![^{]*})')
810 def scons_subst(strSubst
, env
, mode
=SUBST_RAW
, target
=None, source
=None, gvars
={}, lvars
={}, conv
=None, overrides
: Optional
[dict] = None):
811 """Expand a string or list containing construction variable
814 This is the work-horse function for substitutions in file names
815 and the like. The companion scons_subst_list() function (below)
816 handles separating command lines into lists of arguments, so see
817 that function if that's what you're looking for.
819 if (isinstance(strSubst
, str) and '$' not in strSubst
) or isinstance(strSubst
, CmdStringHolder
):
823 conv
= _strconv
[mode
]
825 # Doing this every time is a bit of a waste, since the Executor
826 # has typically already populated the OverrideEnvironment with
827 # $TARGET/$SOURCE variables. We're keeping this (for now), though,
828 # because it supports existing behavior that allows us to call
829 # an Action directly with an arbitrary target+source pair, which
830 # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
831 # If we dropped that behavior (or found another way to cover it),
832 # we could get rid of this call completely and just rely on the
833 # Executor setting the variables.
834 if 'TARGET' not in lvars
:
835 d
= subst_dict(target
, source
)
840 # Allow last ditch chance to override lvars
842 lvars
.update(overrides
)
844 # We're (most likely) going to eval() things. If Python doesn't
845 # find a __builtins__ value in the global dictionary used for eval(),
846 # it copies the current global values for you. Avoid this by
847 # setting it explicitly and then deleting, so we don't pollute the
848 # construction environment Dictionary(ies) that are typically used
850 gvars
['__builtins__'] = __builtins__
852 ss
= StringSubber(env
, mode
, conv
, gvars
)
853 result
= ss
.substitute(strSubst
, lvars
)
856 del gvars
['__builtins__']
861 if is_String(result
):
862 # Remove $(-$) pairs and any stuff in between,
863 # if that's appropriate.
864 remove
= _regex_remove
[mode
]
866 if mode
== SUBST_SIG
:
867 result
= _list_remove
[mode
](remove
.split(result
))
869 raise SCons
.Errors
.UserError("Unbalanced $(/$) in: " + res
)
870 result
= ' '.join(result
)
872 result
= remove
.sub('', result
)
873 if mode
!= SUBST_RAW
:
874 # Compress strings of white space characters into
876 result
= _space_sep
.sub(' ', result
).strip()
878 # Now replace escaped $'s currently "$$"
879 # This is needed because we now retain $$ instead of
880 # replacing them during substition to avoid
881 # improperly trying to escape "$$(" as being "$("
882 result
= result
.replace('$$','$')
883 elif is_Sequence(result
):
884 remove
= _list_remove
[mode
]
886 result
= remove(result
)
888 raise SCons
.Errors
.UserError("Unbalanced $(/$) in: " + str(res
))
892 def scons_subst_list(strSubst
, env
, mode
=SUBST_RAW
, target
=None, source
=None, gvars
={}, lvars
={}, conv
=None, overrides
: Optional
[dict] = None):
893 """Substitute construction variables in a string (or list or other
894 object) and separate the arguments into a command list.
896 The companion scons_subst() function (above) handles basic
897 substitutions within strings, so see that function instead
898 if that's what you're looking for.
901 conv
= _strconv
[mode
]
903 # Doing this every time is a bit of a waste, since the Executor
904 # has typically already populated the OverrideEnvironment with
905 # $TARGET/$SOURCE variables. We're keeping this (for now), though,
906 # because it supports existing behavior that allows us to call
907 # an Action directly with an arbitrary target+source pair, which
908 # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
909 # If we dropped that behavior (or found another way to cover it),
910 # we could get rid of this call completely and just rely on the
911 # Executor setting the variables.
912 if 'TARGET' not in lvars
:
913 d
= subst_dict(target
, source
)
918 # Allow caller to specify last ditch override of lvars
920 lvars
.update(overrides
)
922 # We're (most likely) going to eval() things. If Python doesn't
923 # find a __builtins__ value in the global dictionary used for eval(),
924 # it copies the current global values for you. Avoid this by
925 # setting it explicitly and then deleting, so we don't pollute the
926 # construction environment Dictionary(ies) that are typically used
928 gvars
['__builtins__'] = __builtins__
930 ls
= ListSubber(env
, mode
, conv
, gvars
)
931 ls
.substitute(strSubst
, lvars
, 0)
934 del gvars
['__builtins__']
940 def scons_subst_once(strSubst
, env
, key
):
941 """Perform single (non-recursive) substitution of a single
942 construction variable keyword.
944 This is used when setting a variable when copying or overriding values
945 in an Environment. We want to capture (expand) the old value before
946 we override it, so people can do things like:
948 env2 = env.Clone(CCFLAGS = '$CCFLAGS -g')
950 We do this with some straightforward, brute-force code here...
952 if isinstance(strSubst
, str) and strSubst
.find('$') < 0:
955 matchlist
= ['$' + key
, '${' + key
+ '}']
956 val
= env
.get(key
, '')
957 def sub_match(match
, val
=val
, matchlist
=matchlist
):
962 return ' '.join(map(str, a
))
966 if is_Sequence(strSubst
):
977 result
.append(_dollar_exps
.sub(sub_match
, arg
))
981 elif is_String(strSubst
):
982 return _dollar_exps
.sub(sub_match
, strSubst
)
988 # indent-tabs-mode:nil
990 # vim: set expandtab tabstop=4 shiftwidth=4: