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 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 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
342
343
344 _rm_split = re.compile(r'(?<!\$)(\$[()])')
345
346
347 _regex_remove = [ _rm, None, _rm_split ]
348
350 return [l for l in list if not l in ('$(', '$)')]
351
353 result = []
354 depth = 0
355 for l in list:
356 if l == '$(':
357 depth += 1
358 elif l == '$)':
359 depth -= 1
360 if depth < 0:
361 break
362 elif depth == 0:
363 result.append(l)
364 if depth != 0:
365 return None
366 return result
367
368
369 _list_remove = [ _rm_list, None, _remove_list ]
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392 _dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}'
393 _dollar_exps = re.compile(r'(%s)' % _dollar_exps_str)
394 _separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str)
395
396
397
398 _space_sep = re.compile(r'[\t ]+(?![^{]*})')
399
400 -def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
401 """Expand a string or list containing construction variable
402 substitutions.
403
404 This is the work-horse function for substitutions in file names
405 and the like. The companion scons_subst_list() function (below)
406 handles separating command lines into lists of arguments, so see
407 that function if that's what you're looking for.
408 """
409 if isinstance(strSubst, str) and strSubst.find('$') < 0:
410 return strSubst
411
412 class StringSubber(object):
413 """A class to construct the results of a scons_subst() call.
414
415 This binds a specific construction environment, mode, target and
416 source with two methods (substitute() and expand()) that handle
417 the expansion.
418 """
419 def __init__(self, env, mode, conv, gvars):
420 self.env = env
421 self.mode = mode
422 self.conv = conv
423 self.gvars = gvars
424
425 def expand(self, s, lvars):
426 """Expand a single "token" as necessary, returning an
427 appropriate string containing the expansion.
428
429 This handles expanding different types of things (strings,
430 lists, callables) appropriately. It calls the wrapper
431 substitute() method to re-expand things as necessary, so that
432 the results of expansions of side-by-side strings still get
433 re-evaluated separately, not smushed together.
434 """
435 if is_String(s):
436 try:
437 s0, s1 = s[:2]
438 except (IndexError, ValueError):
439 return s
440 if s0 != '$':
441 return s
442 if s1 == '$':
443
444
445
446
447 return '$$'
448 elif s1 in '()':
449 return s
450 else:
451 key = s[1:]
452 if key[0] == '{' or '.' in key:
453 if key[0] == '{':
454 key = key[1:-1]
455 try:
456 s = eval(key, self.gvars, lvars)
457 except KeyboardInterrupt:
458 raise
459 except Exception as e:
460 if e.__class__ in AllowableExceptions:
461 return ''
462 raise_exception(e, lvars['TARGETS'], s)
463 else:
464 if key in lvars:
465 s = lvars[key]
466 elif key in self.gvars:
467 s = self.gvars[key]
468 elif not NameError in AllowableExceptions:
469 raise_exception(NameError(key), lvars['TARGETS'], s)
470 else:
471 return ''
472
473
474
475
476
477
478
479
480
481
482
483
484
485 lv = lvars.copy()
486 var = key.split('.')[0]
487 lv[var] = ''
488 return self.substitute(s, lv)
489 elif is_Sequence(s):
490 def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
491 return conv(substitute(l, lvars))
492 return list(map(func, s))
493 elif callable(s):
494 try:
495 s = s(target=lvars['TARGETS'],
496 source=lvars['SOURCES'],
497 env=self.env,
498 for_signature=(self.mode != SUBST_CMD))
499 except TypeError:
500
501
502
503 if self.mode == SUBST_RAW:
504 return s
505 s = self.conv(s)
506 return self.substitute(s, lvars)
507 elif s is None:
508 return ''
509 else:
510 return s
511
512 def substitute(self, args, lvars):
513 """Substitute expansions in an argument or list of arguments.
514
515 This serves as a wrapper for splitting up a string into
516 separate tokens.
517 """
518 if is_String(args) and not isinstance(args, CmdStringHolder):
519 args = str(args)
520 try:
521 def sub_match(match):
522 return self.conv(self.expand(match.group(1), lvars))
523 result = _dollar_exps.sub(sub_match, args)
524 except TypeError:
525
526
527
528
529
530 args = _separate_args.findall(args)
531 result = []
532 for a in args:
533 result.append(self.conv(self.expand(a, lvars)))
534 if len(result) == 1:
535 result = result[0]
536 else:
537 result = ''.join(map(str, result))
538 return result
539 else:
540 return self.expand(args, lvars)
541
542 if conv is None:
543 conv = _strconv[mode]
544
545
546
547
548
549
550
551
552
553
554 if 'TARGET' not in lvars:
555 d = subst_dict(target, source)
556 if d:
557 lvars = lvars.copy()
558 lvars.update(d)
559
560
561
562
563
564
565
566 gvars['__builtins__'] = __builtins__
567
568 ss = StringSubber(env, mode, conv, gvars)
569 result = ss.substitute(strSubst, lvars)
570
571 try:
572 del gvars['__builtins__']
573 except KeyError:
574 pass
575
576 res = result
577 if is_String(result):
578
579
580 remove = _regex_remove[mode]
581 if remove:
582 if mode == SUBST_SIG:
583 result = _list_remove[mode](remove.split(result))
584 if result is None:
585 raise SCons.Errors.UserError("Unbalanced $(/$) in: " + res)
586 result = ' '.join(result)
587 else:
588 result = remove.sub('', result)
589 if mode != SUBST_RAW:
590
591
592 result = _space_sep.sub(' ', result).strip()
593
594
595
596
597
598 result = result.replace('$$','$')
599 elif is_Sequence(result):
600 remove = _list_remove[mode]
601 if remove:
602 result = remove(result)
603 if result is None:
604 raise SCons.Errors.UserError("Unbalanced $(/$) in: " + str(res))
605
606 return result
607
609 """Substitute construction variables in a string (or list or other
610 object) and separate the arguments into a command list.
611
612 The companion scons_subst() function (above) handles basic
613 substitutions within strings, so see that function instead
614 if that's what you're looking for.
615 """
616 class ListSubber(collections.UserList):
617 """A class to construct the results of a scons_subst_list() call.
618
619 Like StringSubber, this class binds a specific construction
620 environment, mode, target and source with two methods
621 (substitute() and expand()) that handle the expansion.
622
623 In addition, however, this class is used to track the state of
624 the result(s) we're gathering so we can do the appropriate thing
625 whenever we have to append another word to the result--start a new
626 line, start a new word, append to the current word, etc. We do
627 this by setting the "append" attribute to the right method so
628 that our wrapper methods only need ever call ListSubber.append(),
629 and the rest of the object takes care of doing the right thing
630 internally.
631 """
632 def __init__(self, env, mode, conv, gvars):
633 collections.UserList.__init__(self, [])
634 self.env = env
635 self.mode = mode
636 self.conv = conv
637 self.gvars = gvars
638
639 if self.mode == SUBST_RAW:
640 self.add_strip = lambda x: self.append(x)
641 else:
642 self.add_strip = lambda x: None
643 self.in_strip = None
644 self.next_line()
645
646 def expand(self, s, lvars, within_list):
647 """Expand a single "token" as necessary, appending the
648 expansion to the current result.
649
650 This handles expanding different types of things (strings,
651 lists, callables) appropriately. It calls the wrapper
652 substitute() method to re-expand things as necessary, so that
653 the results of expansions of side-by-side strings still get
654 re-evaluated separately, not smushed together.
655 """
656
657 if is_String(s):
658 try:
659 s0, s1 = s[:2]
660 except (IndexError, ValueError):
661 self.append(s)
662 return
663 if s0 != '$':
664 self.append(s)
665 return
666 if s1 == '$':
667 self.append('$')
668 elif s1 == '(':
669 self.open_strip('$(')
670 elif s1 == ')':
671 self.close_strip('$)')
672 else:
673 key = s[1:]
674 if key[0] == '{' or key.find('.') >= 0:
675 if key[0] == '{':
676 key = key[1:-1]
677 try:
678 s = eval(key, self.gvars, lvars)
679 except KeyboardInterrupt:
680 raise
681 except Exception as e:
682 if e.__class__ in AllowableExceptions:
683 return
684 raise_exception(e, lvars['TARGETS'], s)
685 else:
686 if key in lvars:
687 s = lvars[key]
688 elif key in self.gvars:
689 s = self.gvars[key]
690 elif not NameError in AllowableExceptions:
691 raise_exception(NameError(), lvars['TARGETS'], s)
692 else:
693 return
694
695
696
697
698
699
700 lv = lvars.copy()
701 var = key.split('.')[0]
702 lv[var] = ''
703 self.substitute(s, lv, 0)
704 self.this_word()
705 elif is_Sequence(s):
706 for a in s:
707 self.substitute(a, lvars, 1)
708 self.next_word()
709 elif callable(s):
710 try:
711 s = s(target=lvars['TARGETS'],
712 source=lvars['SOURCES'],
713 env=self.env,
714 for_signature=(self.mode != SUBST_CMD))
715 except TypeError:
716
717
718
719 if self.mode == SUBST_RAW:
720 self.append(s)
721 return
722 s = self.conv(s)
723 self.substitute(s, lvars, within_list)
724 elif s is None:
725 self.this_word()
726 else:
727 self.append(s)
728
729 def substitute(self, args, lvars, within_list):
730 """Substitute expansions in an argument or list of arguments.
731
732 This serves as a wrapper for splitting up a string into
733 separate tokens.
734 """
735
736 if is_String(args) and not isinstance(args, CmdStringHolder):
737 args = str(args)
738 args = _separate_args.findall(args)
739 for a in args:
740 if a[0] in ' \t\n\r\f\v':
741 if '\n' in a:
742 self.next_line()
743 elif within_list:
744 self.append(a)
745 else:
746 self.next_word()
747 else:
748 self.expand(a, lvars, within_list)
749 else:
750 self.expand(args, lvars, within_list)
751
752 def next_line(self):
753 """Arrange for the next word to start a new line. This
754 is like starting a new word, except that we have to append
755 another line to the result."""
756 collections.UserList.append(self, [])
757 self.next_word()
758
759 def this_word(self):
760 """Arrange for the next word to append to the end of the
761 current last word in the result."""
762 self.append = self.add_to_current_word
763
764 def next_word(self):
765 """Arrange for the next word to start a new word."""
766 self.append = self.add_new_word
767
768 def add_to_current_word(self, x):
769 """Append the string x to the end of the current last word
770 in the result. If that is not possible, then just add
771 it as a new word. Make sure the entire concatenated string
772 inherits the object attributes of x (in particular, the
773 escape function) by wrapping it as CmdStringHolder."""
774
775 if not self.in_strip or self.mode != SUBST_SIG:
776 try:
777 current_word = self[-1][-1]
778 except IndexError:
779 self.add_new_word(x)
780 else:
781
782
783
784
785
786
787
788
789
790
791 try:
792 last_char = str(current_word)[-1]
793 except IndexError:
794 last_char = '\0'
795 if last_char in '<>|':
796 self.add_new_word(x)
797 else:
798 y = current_word + x
799
800
801
802
803
804
805
806
807
808
809
810 y = self.conv(y)
811 if is_String(y):
812
813 y = CmdStringHolder(y, None)
814 self[-1][-1] = y
815
816 def add_new_word(self, x):
817 if not self.in_strip or self.mode != SUBST_SIG:
818 literal = self.literal(x)
819 x = self.conv(x)
820 if is_String(x):
821 x = CmdStringHolder(x, literal)
822 self[-1].append(x)
823 self.append = self.add_to_current_word
824
825 def literal(self, x):
826 try:
827 l = x.is_literal
828 except AttributeError:
829 return None
830 else:
831 return l()
832
833 def open_strip(self, x):
834 """Handle the "open strip" $( token."""
835 self.add_strip(x)
836 self.in_strip = 1
837
838 def close_strip(self, x):
839 """Handle the "close strip" $) token."""
840 self.add_strip(x)
841 self.in_strip = None
842
843 if conv is None:
844 conv = _strconv[mode]
845
846
847
848
849
850
851
852
853
854
855 if 'TARGET' not in lvars:
856 d = subst_dict(target, source)
857 if d:
858 lvars = lvars.copy()
859 lvars.update(d)
860
861
862
863
864
865
866
867 gvars['__builtins__'] = __builtins__
868
869 ls = ListSubber(env, mode, conv, gvars)
870 ls.substitute(strSubst, lvars, 0)
871
872 try:
873 del gvars['__builtins__']
874 except KeyError:
875 pass
876
877 return ls.data
878
880 """Perform single (non-recursive) substitution of a single
881 construction variable keyword.
882
883 This is used when setting a variable when copying or overriding values
884 in an Environment. We want to capture (expand) the old value before
885 we override it, so people can do things like:
886
887 env2 = env.Clone(CCFLAGS = '$CCFLAGS -g')
888
889 We do this with some straightforward, brute-force code here...
890 """
891 if isinstance(strSubst, str) and strSubst.find('$') < 0:
892 return strSubst
893
894 matchlist = ['$' + key, '${' + key + '}']
895 val = env.get(key, '')
896 def sub_match(match, val=val, matchlist=matchlist):
897 a = match.group(1)
898 if a in matchlist:
899 a = val
900 if is_Sequence(a):
901 return ' '.join(map(str, a))
902 else:
903 return str(a)
904
905 if is_Sequence(strSubst):
906 result = []
907 for arg in strSubst:
908 if is_String(arg):
909 if arg in matchlist:
910 arg = val
911 if is_Sequence(arg):
912 result.extend(arg)
913 else:
914 result.append(arg)
915 else:
916 result.append(_dollar_exps.sub(sub_match, arg))
917 else:
918 result.append(arg)
919 return result
920 elif is_String(strSubst):
921 return _dollar_exps.sub(sub_match, strSubst)
922 else:
923 return strSubst
924
925
926
927
928
929
930