1 """SCons.Subst
2
3 SCons string substitution.
4
5 """
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 __revision__ = "src/engine/SCons/Subst.py rel_2.5.1:3735:9dc6cee5c168 2016/11/03 14:02:02 bdbaddog"
30
31 import collections
32 import re
33
34 import SCons.Errors
35
36 from SCons.Util import is_String, is_Sequence
37
38
39 _strconv = [SCons.Util.to_String_for_subst,
40 SCons.Util.to_String_for_subst,
41 SCons.Util.to_String_for_signature]
42
43
44
45 AllowableExceptions = (IndexError, NameError)
46
50
58
59
60
62 """A wrapper for a string. If you use this object wrapped
63 around a string, then it will be interpreted as literal.
64 When passed to the command interpreter, all special
65 characters will be escaped."""
68
71
72 - def escape(self, escape_func):
73 return escape_func(self.lstr)
74
77
80
82 if not isinstance(other, Literal):
83 return False
84 return self.lstr == other.lstr
85
87 return not self.__eq__(other)
88
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."""
97
98 - def __init__(self, lstr, for_signature=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."""
102 self.lstr = lstr
103 if for_signature:
104 self.forsig = for_signature
105 else:
106 self.forsig = lstr
107
110
111 - def escape(self, escape_func):
112 return escape_func(self.lstr)
113
116
119
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:
124 return '"%s"' % arg
125 else:
126 return str(arg)
127
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.
134 """
136 collections.UserString.__init__(self, cmd)
137 self.literal = literal
138
141
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.
147
148 After calling this function, the next call to str() will
149 return the escaped string.
150 """
151
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)
156 else:
157 return self.data
158
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):
163 try:
164 e = obj.escape
165 except AttributeError:
166 return obj
167 else:
168 return e(escape_func)
169 return list(map(escape, mylist))
170
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.
179
180 In practice, this might be a wash performance-wise, but it's a little
181 cleaner conceptually...
182 """
183
185 self.list = list
186 self.func = func
190 mylist = self.list
191 if mylist is None:
192 mylist = []
193 elif not is_Sequence(mylist):
194 mylist = [mylist]
195
196
197 self.nodelist = SCons.Util.NodeList(list(map(self.func, mylist)))
198 self._create_nodelist = self._return_nodelist
199 return self.nodelist
200 _create_nodelist = _gen_nodelist
201
202
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.
207
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.
212 """
216 nl = self.nl._create_nodelist()
217 return getattr(nl, attr)
219 nl = self.nl._create_nodelist()
220 return nl[i]
222 nl = self.nl._create_nodelist()
223 i = max(i, 0); j = max(j, 0)
224 return nl[i:j]
226 nl = self.nl._create_nodelist()
227 return str(nl)
229 nl = self.nl._create_nodelist()
230 return repr(nl)
231
233 """A class that implements $TARGET or $SOURCE expansions by in turn
234 wrapping a NLWrapper. This class handles the different methods used
235 to access an individual proxy Node, calling the NLWrapper to create
236 a proxy on demand.
237 """
241 nl = self.nl._create_nodelist()
242 try:
243 nl0 = nl[0]
244 except IndexError:
245
246
247 raise AttributeError("NodeList has no attribute: %s" % attr)
248 return getattr(nl0, attr)
250 nl = self.nl._create_nodelist()
251 if nl:
252 return str(nl[0])
253 return ''
255 nl = self.nl._create_nodelist()
256 if nl:
257 return repr(nl[0])
258 return ''
259
261 - def __call__(self, *args, **kwargs): return ''
263
264 NullNodesList = NullNodeList()
265
267 """Create a dictionary for substitution of special
268 construction variables.
269
270 This translates the following special arguments:
271
272 target - the target (object or array of objects),
273 used to generate the TARGET and TARGETS
274 construction variables
275
276 source - the source (object or array of objects),
277 used to generate the SOURCES and SOURCE
278 construction variables
279 """
280 dict = {}
281
282 if target:
283 def get_tgt_subst_proxy(thing):
284 try:
285 subst_proxy = thing.get_subst_proxy()
286 except AttributeError:
287 subst_proxy = thing
288 return subst_proxy
289 tnl = NLWrapper(target, get_tgt_subst_proxy)
290 dict['TARGETS'] = Targets_or_Sources(tnl)
291 dict['TARGET'] = Target_or_Source(tnl)
292
293
294
295
296
297 dict['CHANGED_TARGETS'] = '$TARGETS'
298 dict['UNCHANGED_TARGETS'] = '$TARGETS'
299 else:
300 dict['TARGETS'] = NullNodesList
301 dict['TARGET'] = NullNodesList
302
303 if source:
304 def get_src_subst_proxy(node):
305 try:
306 rfile = node.rfile
307 except AttributeError:
308 pass
309 else:
310 node = rfile()
311 try:
312 return node.get_subst_proxy()
313 except AttributeError:
314 return node
315 snl = NLWrapper(source, get_src_subst_proxy)
316 dict['SOURCES'] = Targets_or_Sources(snl)
317 dict['SOURCE'] = Target_or_Source(snl)
318
319
320
321
322
323 dict['CHANGED_SOURCES'] = '$SOURCES'
324 dict['UNCHANGED_SOURCES'] = '$SOURCES'
325 else:
326 dict['SOURCES'] = NullNodesList
327 dict['SOURCE'] = NullNodesList
328
329 return dict
330
331
332
333
334
335
336 SUBST_CMD = 0
337 SUBST_RAW = 1
338 SUBST_SIG = 2
339
340 _rm = re.compile(r'\$[()]')
341 _remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)')
342
343
344 _regex_remove = [ _rm, None, _remove ]
345
347 return [l for l in list if not l in ('$(', '$)')]
348
360
361
362 _list_remove = [ _rm_list, None, _remove_list ]
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
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)
388
389
390
391 _space_sep = re.compile(r'[\t ]+(?![^{]*})')
392
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
395 substitutions.
396
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.
401 """
402 if isinstance(strSubst, str) and strSubst.find('$') < 0:
403 return strSubst
404
405 class StringSubber(object):
406 """A class to construct the results of a scons_subst() call.
407
408 This binds a specific construction environment, mode, target and
409 source with two methods (substitute() and expand()) that handle
410 the expansion.
411 """
412 def __init__(self, env, mode, conv, gvars):
413 self.env = env
414 self.mode = mode
415 self.conv = conv
416 self.gvars = gvars
417
418 def expand(self, s, lvars):
419 """Expand a single "token" as necessary, returning an
420 appropriate string containing the expansion.
421
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.
427 """
428 if is_String(s):
429 try:
430 s0, s1 = s[:2]
431 except (IndexError, ValueError):
432 return s
433 if s0 != '$':
434 return s
435 if s1 == '$':
436 return '$'
437 elif s1 in '()':
438 return s
439 else:
440 key = s[1:]
441 if key[0] == '{' or key.find('.') >= 0:
442 if key[0] == '{':
443 key = key[1:-1]
444 try:
445 s = eval(key, self.gvars, lvars)
446 except KeyboardInterrupt:
447 raise
448 except Exception, e:
449 if e.__class__ in AllowableExceptions:
450 return ''
451 raise_exception(e, lvars['TARGETS'], s)
452 else:
453 if key in lvars:
454 s = lvars[key]
455 elif key in self.gvars:
456 s = self.gvars[key]
457 elif not NameError in AllowableExceptions:
458 raise_exception(NameError(key), lvars['TARGETS'], s)
459 else:
460 return ''
461
462
463
464
465
466
467
468
469
470
471
472
473
474 lv = lvars.copy()
475 var = key.split('.')[0]
476 lv[var] = ''
477 return self.substitute(s, lv)
478 elif is_Sequence(s):
479 def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
480 return conv(substitute(l, lvars))
481 return list(map(func, s))
482 elif callable(s):
483 try:
484 s = s(target=lvars['TARGETS'],
485 source=lvars['SOURCES'],
486 env=self.env,
487 for_signature=(self.mode != SUBST_CMD))
488 except TypeError:
489
490
491
492 if self.mode == SUBST_RAW:
493 return s
494 s = self.conv(s)
495 return self.substitute(s, lvars)
496 elif s is None:
497 return ''
498 else:
499 return s
500
501 def substitute(self, args, lvars):
502 """Substitute expansions in an argument or list of arguments.
503
504 This serves as a wrapper for splitting up a string into
505 separate tokens.
506 """
507 if is_String(args) and not isinstance(args, CmdStringHolder):
508 args = str(args)
509 try:
510 def sub_match(match):
511 return self.conv(self.expand(match.group(1), lvars))
512 result = _dollar_exps.sub(sub_match, args)
513 except TypeError:
514
515
516
517
518
519 args = _separate_args.findall(args)
520 result = []
521 for a in args:
522 result.append(self.conv(self.expand(a, lvars)))
523 if len(result) == 1:
524 result = result[0]
525 else:
526 result = ''.join(map(str, result))
527 return result
528 else:
529 return self.expand(args, lvars)
530
531 if conv is None:
532 conv = _strconv[mode]
533
534
535
536
537
538
539
540
541
542
543 if 'TARGET' not in lvars:
544 d = subst_dict(target, source)
545 if d:
546 lvars = lvars.copy()
547 lvars.update(d)
548
549
550
551
552
553
554
555 gvars['__builtins__'] = __builtins__
556
557 ss = StringSubber(env, mode, conv, gvars)
558 result = ss.substitute(strSubst, lvars)
559
560 try:
561 del gvars['__builtins__']
562 except KeyError:
563 pass
564
565 if is_String(result):
566
567
568 remove = _regex_remove[mode]
569 if remove:
570 result = remove.sub('', result)
571 if mode != SUBST_RAW:
572
573
574 result = _space_sep.sub(' ', result).strip()
575 elif is_Sequence(result):
576 remove = _list_remove[mode]
577 if remove:
578 result = remove(result)
579
580 return result
581
583 """Substitute construction variables in a string (or list or other
584 object) and separate the arguments into a command list.
585
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.
589 """
590 class ListSubber(collections.UserList):
591 """A class to construct the results of a scons_subst_list() call.
592
593 Like StringSubber, this class binds a specific construction
594 environment, mode, target and source with two methods
595 (substitute() and expand()) that handle the expansion.
596
597 In addition, however, this class is used to track the state of
598 the result(s) we're gathering so we can do the appropriate thing
599 whenever we have to append another word to the result--start a new
600 line, start a new word, append to the current word, etc. We do
601 this by setting the "append" attribute to the right method so
602 that our wrapper methods only need ever call ListSubber.append(),
603 and the rest of the object takes care of doing the right thing
604 internally.
605 """
606 def __init__(self, env, mode, conv, gvars):
607 collections.UserList.__init__(self, [])
608 self.env = env
609 self.mode = mode
610 self.conv = conv
611 self.gvars = gvars
612
613 if self.mode == SUBST_RAW:
614 self.add_strip = lambda x: self.append(x)
615 else:
616 self.add_strip = lambda x: None
617 self.in_strip = None
618 self.next_line()
619
620 def expand(self, s, lvars, within_list):
621 """Expand a single "token" as necessary, appending the
622 expansion to the current result.
623
624 This handles expanding different types of things (strings,
625 lists, callables) appropriately. It calls the wrapper
626 substitute() method to re-expand things as necessary, so that
627 the results of expansions of side-by-side strings still get
628 re-evaluated separately, not smushed together.
629 """
630
631 if is_String(s):
632 try:
633 s0, s1 = s[:2]
634 except (IndexError, ValueError):
635 self.append(s)
636 return
637 if s0 != '$':
638 self.append(s)
639 return
640 if s1 == '$':
641 self.append('$')
642 elif s1 == '(':
643 self.open_strip('$(')
644 elif s1 == ')':
645 self.close_strip('$)')
646 else:
647 key = s[1:]
648 if key[0] == '{' or key.find('.') >= 0:
649 if key[0] == '{':
650 key = key[1:-1]
651 try:
652 s = eval(key, self.gvars, lvars)
653 except KeyboardInterrupt:
654 raise
655 except Exception, e:
656 if e.__class__ in AllowableExceptions:
657 return
658 raise_exception(e, lvars['TARGETS'], s)
659 else:
660 if key in lvars:
661 s = lvars[key]
662 elif key in self.gvars:
663 s = self.gvars[key]
664 elif not NameError in AllowableExceptions:
665 raise_exception(NameError(), lvars['TARGETS'], s)
666 else:
667 return
668
669
670
671
672
673
674 lv = lvars.copy()
675 var = key.split('.')[0]
676 lv[var] = ''
677 self.substitute(s, lv, 0)
678 self.this_word()
679 elif is_Sequence(s):
680 for a in s:
681 self.substitute(a, lvars, 1)
682 self.next_word()
683 elif callable(s):
684 try:
685 s = s(target=lvars['TARGETS'],
686 source=lvars['SOURCES'],
687 env=self.env,
688 for_signature=(self.mode != SUBST_CMD))
689 except TypeError:
690
691
692
693 if self.mode == SUBST_RAW:
694 self.append(s)
695 return
696 s = self.conv(s)
697 self.substitute(s, lvars, within_list)
698 elif s is None:
699 self.this_word()
700 else:
701 self.append(s)
702
703 def substitute(self, args, lvars, within_list):
704 """Substitute expansions in an argument or list of arguments.
705
706 This serves as a wrapper for splitting up a string into
707 separate tokens.
708 """
709
710 if is_String(args) and not isinstance(args, CmdStringHolder):
711 args = str(args)
712 args = _separate_args.findall(args)
713 for a in args:
714 if a[0] in ' \t\n\r\f\v':
715 if '\n' in a:
716 self.next_line()
717 elif within_list:
718 self.append(a)
719 else:
720 self.next_word()
721 else:
722 self.expand(a, lvars, within_list)
723 else:
724 self.expand(args, lvars, within_list)
725
726 def next_line(self):
727 """Arrange for the next word to start a new line. This
728 is like starting a new word, except that we have to append
729 another line to the result."""
730 collections.UserList.append(self, [])
731 self.next_word()
732
733 def this_word(self):
734 """Arrange for the next word to append to the end of the
735 current last word in the result."""
736 self.append = self.add_to_current_word
737
738 def next_word(self):
739 """Arrange for the next word to start a new word."""
740 self.append = self.add_new_word
741
742 def add_to_current_word(self, x):
743 """Append the string x to the end of the current last word
744 in the result. If that is not possible, then just add
745 it as a new word. Make sure the entire concatenated string
746 inherits the object attributes of x (in particular, the
747 escape function) by wrapping it as CmdStringHolder."""
748
749 if not self.in_strip or self.mode != SUBST_SIG:
750 try:
751 current_word = self[-1][-1]
752 except IndexError:
753 self.add_new_word(x)
754 else:
755
756
757
758
759
760
761
762
763
764
765 try:
766 last_char = str(current_word)[-1]
767 except IndexError:
768 last_char = '\0'
769 if last_char in '<>|':
770 self.add_new_word(x)
771 else:
772 y = current_word + x
773
774
775
776
777
778
779
780
781
782
783
784 y = self.conv(y)
785 if is_String(y):
786
787 y = CmdStringHolder(y, None)
788 self[-1][-1] = y
789
790 def add_new_word(self, x):
791 if not self.in_strip or self.mode != SUBST_SIG:
792 literal = self.literal(x)
793 x = self.conv(x)
794 if is_String(x):
795 x = CmdStringHolder(x, literal)
796 self[-1].append(x)
797 self.append = self.add_to_current_word
798
799 def literal(self, x):
800 try:
801 l = x.is_literal
802 except AttributeError:
803 return None
804 else:
805 return l()
806
807 def open_strip(self, x):
808 """Handle the "open strip" $( token."""
809 self.add_strip(x)
810 self.in_strip = 1
811
812 def close_strip(self, x):
813 """Handle the "close strip" $) token."""
814 self.add_strip(x)
815 self.in_strip = None
816
817 if conv is None:
818 conv = _strconv[mode]
819
820
821
822
823
824
825
826
827
828
829 if 'TARGET' not in lvars:
830 d = subst_dict(target, source)
831 if d:
832 lvars = lvars.copy()
833 lvars.update(d)
834
835
836
837
838
839
840
841 gvars['__builtins__'] = __builtins__
842
843 ls = ListSubber(env, mode, conv, gvars)
844 ls.substitute(strSubst, lvars, 0)
845
846 try:
847 del gvars['__builtins__']
848 except KeyError:
849 pass
850
851 return ls.data
852
854 """Perform single (non-recursive) substitution of a single
855 construction variable keyword.
856
857 This is used when setting a variable when copying or overriding values
858 in an Environment. We want to capture (expand) the old value before
859 we override it, so people can do things like:
860
861 env2 = env.Clone(CCFLAGS = '$CCFLAGS -g')
862
863 We do this with some straightforward, brute-force code here...
864 """
865 if isinstance(strSubst, str) and strSubst.find('$') < 0:
866 return strSubst
867
868 matchlist = ['$' + key, '${' + key + '}']
869 val = env.get(key, '')
870 def sub_match(match, val=val, matchlist=matchlist):
871 a = match.group(1)
872 if a in matchlist:
873 a = val
874 if is_Sequence(a):
875 return ' '.join(map(str, a))
876 else:
877 return str(a)
878
879 if is_Sequence(strSubst):
880 result = []
881 for arg in strSubst:
882 if is_String(arg):
883 if arg in matchlist:
884 arg = val
885 if is_Sequence(arg):
886 result.extend(arg)
887 else:
888 result.append(arg)
889 else:
890 result.append(_dollar_exps.sub(sub_match, arg))
891 else:
892 result.append(arg)
893 return result
894 elif is_String(strSubst):
895 return _dollar_exps.sub(sub_match, strSubst)
896 else:
897 return strSubst
898
899
900
901
902
903
904