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 3a41ed6b288cee8d085373ad7fa02894e1903864 2019-01-23 17:30:35 bdeegan"
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 return hash(self.lstr)
91
93 """This is a wrapper for what we call a 'Node special attribute.'
94 This is any of the attributes of a Node that we can reference from
95 Environment variable substitution, such as $TARGET.abspath or
96 $SOURCES[1].filebase. We implement the same methods as Literal
97 so we can handle special characters, plus a for_signature method,
98 such that we can return some canonical string during signature
99 calculation to avoid unnecessary rebuilds."""
100
101 - def __init__(self, lstr, for_signature=None):
102 """The for_signature parameter, if supplied, will be the
103 canonical string we return from for_signature(). Else
104 we will simply return lstr."""
105 self.lstr = lstr
106 if for_signature:
107 self.forsig = for_signature
108 else:
109 self.forsig = lstr
110
113
114 - def escape(self, escape_func):
115 return escape_func(self.lstr)
116
119
122
124 """Generic function for putting double quotes around any string that
125 has white space in it."""
126 if ' ' in arg or '\t' in arg:
127 return '"%s"' % arg
128 else:
129 return str(arg)
130
132 """This is a special class used to hold strings generated by
133 scons_subst() and scons_subst_list(). It defines a special method
134 escape(). When passed a function with an escape algorithm for a
135 particular platform, it will return the contained string with the
136 proper escape sequences inserted.
137 """
139 collections.UserString.__init__(self, cmd)
140 self.literal = literal
141
144
145 - def escape(self, escape_func, quote_func=quote_spaces):
146 """Escape the string with the supplied function. The
147 function is expected to take an arbitrary string, then
148 return it with all special characters escaped and ready
149 for passing to the command interpreter.
150
151 After calling this function, the next call to str() will
152 return the escaped string.
153 """
154
155 if self.is_literal():
156 return escape_func(self.data)
157 elif ' ' in self.data or '\t' in self.data:
158 return quote_func(self.data)
159 else:
160 return self.data
161
163 """Escape a list of arguments by running the specified escape_func
164 on every object in the list that has an escape() method."""
165 def escape(obj, escape_func=escape_func):
166 try:
167 e = obj.escape
168 except AttributeError:
169 return obj
170 else:
171 return e(escape_func)
172 return list(map(escape, mylist))
173
175 """A wrapper class that delays turning a list of sources or targets
176 into a NodeList until it's needed. The specified function supplied
177 when the object is initialized is responsible for turning raw nodes
178 into proxies that implement the special attributes like .abspath,
179 .source, etc. This way, we avoid creating those proxies just
180 "in case" someone is going to use $TARGET or the like, and only
181 go through the trouble if we really have to.
182
183 In practice, this might be a wash performance-wise, but it's a little
184 cleaner conceptually...
185 """
186
188 self.list = list
189 self.func = func
193 mylist = self.list
194 if mylist is None:
195 mylist = []
196 elif not is_Sequence(mylist):
197 mylist = [mylist]
198
199
200 self.nodelist = SCons.Util.NodeList(list(map(self.func, mylist)))
201 self._create_nodelist = self._return_nodelist
202 return self.nodelist
203 _create_nodelist = _gen_nodelist
204
205
207 """A class that implements $TARGETS or $SOURCES expansions by in turn
208 wrapping a NLWrapper. This class handles the different methods used
209 to access the list, calling the NLWrapper to create proxies on demand.
210
211 Note that we subclass collections.UserList purely so that the
212 is_Sequence() function will identify an object of this class as
213 a list during variable expansion. We're not really using any
214 collections.UserList methods in practice.
215 """
219 nl = self.nl._create_nodelist()
220 return getattr(nl, attr)
222 nl = self.nl._create_nodelist()
223 return nl[i]
225 nl = self.nl._create_nodelist()
226 i = max(i, 0); j = max(j, 0)
227 return nl[i:j]
229 nl = self.nl._create_nodelist()
230 return str(nl)
232 nl = self.nl._create_nodelist()
233 return repr(nl)
234
236 """A class that implements $TARGET or $SOURCE expansions by in turn
237 wrapping a NLWrapper. This class handles the different methods used
238 to access an individual proxy Node, calling the NLWrapper to create
239 a proxy on demand.
240 """
244 nl = self.nl._create_nodelist()
245 try:
246 nl0 = nl[0]
247 except IndexError:
248
249
250 raise AttributeError("NodeList has no attribute: %s" % attr)
251 return getattr(nl0, attr)
253 nl = self.nl._create_nodelist()
254 if nl:
255 return str(nl[0])
256 return ''
258 nl = self.nl._create_nodelist()
259 if nl:
260 return repr(nl[0])
261 return ''
262
264 - def __call__(self, *args, **kwargs): return ''
266
267 NullNodesList = NullNodeList()
268
270 """Create a dictionary for substitution of special
271 construction variables.
272
273 This translates the following special arguments:
274
275 target - the target (object or array of objects),
276 used to generate the TARGET and TARGETS
277 construction variables
278
279 source - the source (object or array of objects),
280 used to generate the SOURCES and SOURCE
281 construction variables
282 """
283 dict = {}
284
285 if target:
286 def get_tgt_subst_proxy(thing):
287 try:
288 subst_proxy = thing.get_subst_proxy()
289 except AttributeError:
290 subst_proxy = thing
291 return subst_proxy
292 tnl = NLWrapper(target, get_tgt_subst_proxy)
293 dict['TARGETS'] = Targets_or_Sources(tnl)
294 dict['TARGET'] = Target_or_Source(tnl)
295
296
297
298
299
300 dict['CHANGED_TARGETS'] = '$TARGETS'
301 dict['UNCHANGED_TARGETS'] = '$TARGETS'
302 else:
303 dict['TARGETS'] = NullNodesList
304 dict['TARGET'] = NullNodesList
305
306 if source:
307 def get_src_subst_proxy(node):
308 try:
309 rfile = node.rfile
310 except AttributeError:
311 pass
312 else:
313 node = rfile()
314 try:
315 return node.get_subst_proxy()
316 except AttributeError:
317 return node
318 snl = NLWrapper(source, get_src_subst_proxy)
319 dict['SOURCES'] = Targets_or_Sources(snl)
320 dict['SOURCE'] = Target_or_Source(snl)
321
322
323
324
325
326 dict['CHANGED_SOURCES'] = '$SOURCES'
327 dict['UNCHANGED_SOURCES'] = '$SOURCES'
328 else:
329 dict['SOURCES'] = NullNodesList
330 dict['SOURCE'] = NullNodesList
331
332 return dict
333
334
335
336
337
338
339 SUBST_CMD = 0
340 SUBST_RAW = 1
341 SUBST_SIG = 2
342
343 _rm = re.compile(r'\$[()]')
344
345
346
347 _rm_split = re.compile(r'(?<!\$)(\$[()])')
348
349
350 _regex_remove = [ _rm, None, _rm_split ]
351
353 return [l for l in list if not l in ('$(', '$)')]
354
356 result = []
357 depth = 0
358 for l in list:
359 if l == '$(':
360 depth += 1
361 elif l == '$)':
362 depth -= 1
363 if depth < 0:
364 break
365 elif depth == 0:
366 result.append(l)
367 if depth != 0:
368 return None
369 return result
370
371
372 _list_remove = [ _rm_list, None, _remove_list ]
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395 _dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}'
396 _dollar_exps = re.compile(r'(%s)' % _dollar_exps_str)
397 _separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str)
398
399
400
401 _space_sep = re.compile(r'[\t ]+(?![^{]*})')
402
403 -def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
404 """Expand a string or list containing construction variable
405 substitutions.
406
407 This is the work-horse function for substitutions in file names
408 and the like. The companion scons_subst_list() function (below)
409 handles separating command lines into lists of arguments, so see
410 that function if that's what you're looking for.
411 """
412 if isinstance(strSubst, str) and strSubst.find('$') < 0:
413 return strSubst
414
415 class StringSubber(object):
416 """A class to construct the results of a scons_subst() call.
417
418 This binds a specific construction environment, mode, target and
419 source with two methods (substitute() and expand()) that handle
420 the expansion.
421 """
422 def __init__(self, env, mode, conv, gvars):
423 self.env = env
424 self.mode = mode
425 self.conv = conv
426 self.gvars = gvars
427
428 def expand(self, s, lvars):
429 """Expand a single "token" as necessary, returning an
430 appropriate string containing the expansion.
431
432 This handles expanding different types of things (strings,
433 lists, callables) appropriately. It calls the wrapper
434 substitute() method to re-expand things as necessary, so that
435 the results of expansions of side-by-side strings still get
436 re-evaluated separately, not smushed together.
437 """
438 if is_String(s):
439 try:
440 s0, s1 = s[:2]
441 except (IndexError, ValueError):
442 return s
443 if s0 != '$':
444 return s
445 if s1 == '$':
446
447
448
449
450 return '$$'
451 elif s1 in '()':
452 return s
453 else:
454 key = s[1:]
455 if key[0] == '{' or '.' in key:
456 if key[0] == '{':
457 key = key[1:-1]
458 try:
459 s = eval(key, self.gvars, lvars)
460 except KeyboardInterrupt:
461 raise
462 except Exception as e:
463 if e.__class__ in AllowableExceptions:
464 return ''
465 raise_exception(e, lvars['TARGETS'], s)
466 else:
467 if key in lvars:
468 s = lvars[key]
469 elif key in self.gvars:
470 s = self.gvars[key]
471 elif not NameError in AllowableExceptions:
472 raise_exception(NameError(key), lvars['TARGETS'], s)
473 else:
474 return ''
475
476
477
478
479
480
481
482
483
484
485
486
487
488 lv = lvars.copy()
489 var = key.split('.')[0]
490 lv[var] = ''
491 return self.substitute(s, lv)
492 elif is_Sequence(s):
493 def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
494 return conv(substitute(l, lvars))
495 return list(map(func, s))
496 elif callable(s):
497 try:
498 s = s(target=lvars['TARGETS'],
499 source=lvars['SOURCES'],
500 env=self.env,
501 for_signature=(self.mode != SUBST_CMD))
502 except TypeError:
503
504
505
506 if self.mode == SUBST_RAW:
507 return s
508 s = self.conv(s)
509 return self.substitute(s, lvars)
510 elif s is None:
511 return ''
512 else:
513 return s
514
515 def substitute(self, args, lvars):
516 """Substitute expansions in an argument or list of arguments.
517
518 This serves as a wrapper for splitting up a string into
519 separate tokens.
520 """
521 if is_String(args) and not isinstance(args, CmdStringHolder):
522 args = str(args)
523 try:
524 def sub_match(match):
525 return self.conv(self.expand(match.group(1), lvars))
526 result = _dollar_exps.sub(sub_match, args)
527 except TypeError:
528
529
530
531
532
533 args = _separate_args.findall(args)
534 result = []
535 for a in args:
536 result.append(self.conv(self.expand(a, lvars)))
537 if len(result) == 1:
538 result = result[0]
539 else:
540 result = ''.join(map(str, result))
541 return result
542 else:
543 return self.expand(args, lvars)
544
545 if conv is None:
546 conv = _strconv[mode]
547
548
549
550
551
552
553
554
555
556
557 if 'TARGET' not in lvars:
558 d = subst_dict(target, source)
559 if d:
560 lvars = lvars.copy()
561 lvars.update(d)
562
563
564
565
566
567
568
569 gvars['__builtins__'] = __builtins__
570
571 ss = StringSubber(env, mode, conv, gvars)
572 result = ss.substitute(strSubst, lvars)
573
574 try:
575 del gvars['__builtins__']
576 except KeyError:
577 pass
578
579 res = result
580 if is_String(result):
581
582
583 remove = _regex_remove[mode]
584 if remove:
585 if mode == SUBST_SIG:
586 result = _list_remove[mode](remove.split(result))
587 if result is None:
588 raise SCons.Errors.UserError("Unbalanced $(/$) in: " + res)
589 result = ' '.join(result)
590 else:
591 result = remove.sub('', result)
592 if mode != SUBST_RAW:
593
594
595 result = _space_sep.sub(' ', result).strip()
596
597
598
599
600
601 result = result.replace('$$','$')
602 elif is_Sequence(result):
603 remove = _list_remove[mode]
604 if remove:
605 result = remove(result)
606 if result is None:
607 raise SCons.Errors.UserError("Unbalanced $(/$) in: " + str(res))
608
609 return result
610
612 """Substitute construction variables in a string (or list or other
613 object) and separate the arguments into a command list.
614
615 The companion scons_subst() function (above) handles basic
616 substitutions within strings, so see that function instead
617 if that's what you're looking for.
618 """
619 class ListSubber(collections.UserList):
620 """A class to construct the results of a scons_subst_list() call.
621
622 Like StringSubber, this class binds a specific construction
623 environment, mode, target and source with two methods
624 (substitute() and expand()) that handle the expansion.
625
626 In addition, however, this class is used to track the state of
627 the result(s) we're gathering so we can do the appropriate thing
628 whenever we have to append another word to the result--start a new
629 line, start a new word, append to the current word, etc. We do
630 this by setting the "append" attribute to the right method so
631 that our wrapper methods only need ever call ListSubber.append(),
632 and the rest of the object takes care of doing the right thing
633 internally.
634 """
635 def __init__(self, env, mode, conv, gvars):
636 collections.UserList.__init__(self, [])
637 self.env = env
638 self.mode = mode
639 self.conv = conv
640 self.gvars = gvars
641
642 if self.mode == SUBST_RAW:
643 self.add_strip = lambda x: self.append(x)
644 else:
645 self.add_strip = lambda x: None
646 self.in_strip = None
647 self.next_line()
648
649 def expand(self, s, lvars, within_list):
650 """Expand a single "token" as necessary, appending the
651 expansion to the current result.
652
653 This handles expanding different types of things (strings,
654 lists, callables) appropriately. It calls the wrapper
655 substitute() method to re-expand things as necessary, so that
656 the results of expansions of side-by-side strings still get
657 re-evaluated separately, not smushed together.
658 """
659
660 if is_String(s):
661 try:
662 s0, s1 = s[:2]
663 except (IndexError, ValueError):
664 self.append(s)
665 return
666 if s0 != '$':
667 self.append(s)
668 return
669 if s1 == '$':
670 self.append('$')
671 elif s1 == '(':
672 self.open_strip('$(')
673 elif s1 == ')':
674 self.close_strip('$)')
675 else:
676 key = s[1:]
677 if key[0] == '{' or key.find('.') >= 0:
678 if key[0] == '{':
679 key = key[1:-1]
680 try:
681 s = eval(key, self.gvars, lvars)
682 except KeyboardInterrupt:
683 raise
684 except Exception as e:
685 if e.__class__ in AllowableExceptions:
686 return
687 raise_exception(e, lvars['TARGETS'], s)
688 else:
689 if key in lvars:
690 s = lvars[key]
691 elif key in self.gvars:
692 s = self.gvars[key]
693 elif not NameError in AllowableExceptions:
694 raise_exception(NameError(), lvars['TARGETS'], s)
695 else:
696 return
697
698
699
700
701
702
703 lv = lvars.copy()
704 var = key.split('.')[0]
705 lv[var] = ''
706 self.substitute(s, lv, 0)
707 self.this_word()
708 elif is_Sequence(s):
709 for a in s:
710 self.substitute(a, lvars, 1)
711 self.next_word()
712 elif callable(s):
713 try:
714 s = s(target=lvars['TARGETS'],
715 source=lvars['SOURCES'],
716 env=self.env,
717 for_signature=(self.mode != SUBST_CMD))
718 except TypeError:
719
720
721
722 if self.mode == SUBST_RAW:
723 self.append(s)
724 return
725 s = self.conv(s)
726 self.substitute(s, lvars, within_list)
727 elif s is None:
728 self.this_word()
729 else:
730 self.append(s)
731
732 def substitute(self, args, lvars, within_list):
733 """Substitute expansions in an argument or list of arguments.
734
735 This serves as a wrapper for splitting up a string into
736 separate tokens.
737 """
738
739 if is_String(args) and not isinstance(args, CmdStringHolder):
740 args = str(args)
741 args = _separate_args.findall(args)
742 for a in args:
743 if a[0] in ' \t\n\r\f\v':
744 if '\n' in a:
745 self.next_line()
746 elif within_list:
747 self.append(a)
748 else:
749 self.next_word()
750 else:
751 self.expand(a, lvars, within_list)
752 else:
753 self.expand(args, lvars, within_list)
754
755 def next_line(self):
756 """Arrange for the next word to start a new line. This
757 is like starting a new word, except that we have to append
758 another line to the result."""
759 collections.UserList.append(self, [])
760 self.next_word()
761
762 def this_word(self):
763 """Arrange for the next word to append to the end of the
764 current last word in the result."""
765 self.append = self.add_to_current_word
766
767 def next_word(self):
768 """Arrange for the next word to start a new word."""
769 self.append = self.add_new_word
770
771 def add_to_current_word(self, x):
772 """Append the string x to the end of the current last word
773 in the result. If that is not possible, then just add
774 it as a new word. Make sure the entire concatenated string
775 inherits the object attributes of x (in particular, the
776 escape function) by wrapping it as CmdStringHolder."""
777
778 if not self.in_strip or self.mode != SUBST_SIG:
779 try:
780 current_word = self[-1][-1]
781 except IndexError:
782 self.add_new_word(x)
783 else:
784
785
786
787
788
789
790
791
792
793
794 try:
795 last_char = str(current_word)[-1]
796 except IndexError:
797 last_char = '\0'
798 if last_char in '<>|':
799 self.add_new_word(x)
800 else:
801 y = current_word + x
802
803
804
805
806
807
808
809
810
811
812
813 y = self.conv(y)
814 if is_String(y):
815
816 y = CmdStringHolder(y, None)
817 self[-1][-1] = y
818
819 def add_new_word(self, x):
820 if not self.in_strip or self.mode != SUBST_SIG:
821 literal = self.literal(x)
822 x = self.conv(x)
823 if is_String(x):
824 x = CmdStringHolder(x, literal)
825 self[-1].append(x)
826 self.append = self.add_to_current_word
827
828 def literal(self, x):
829 try:
830 l = x.is_literal
831 except AttributeError:
832 return None
833 else:
834 return l()
835
836 def open_strip(self, x):
837 """Handle the "open strip" $( token."""
838 self.add_strip(x)
839 self.in_strip = 1
840
841 def close_strip(self, x):
842 """Handle the "close strip" $) token."""
843 self.add_strip(x)
844 self.in_strip = None
845
846 if conv is None:
847 conv = _strconv[mode]
848
849
850
851
852
853
854
855
856
857
858 if 'TARGET' not in lvars:
859 d = subst_dict(target, source)
860 if d:
861 lvars = lvars.copy()
862 lvars.update(d)
863
864
865
866
867
868
869
870 gvars['__builtins__'] = __builtins__
871
872 ls = ListSubber(env, mode, conv, gvars)
873 ls.substitute(strSubst, lvars, 0)
874
875 try:
876 del gvars['__builtins__']
877 except KeyError:
878 pass
879
880 return ls.data
881
883 """Perform single (non-recursive) substitution of a single
884 construction variable keyword.
885
886 This is used when setting a variable when copying or overriding values
887 in an Environment. We want to capture (expand) the old value before
888 we override it, so people can do things like:
889
890 env2 = env.Clone(CCFLAGS = '$CCFLAGS -g')
891
892 We do this with some straightforward, brute-force code here...
893 """
894 if isinstance(strSubst, str) and strSubst.find('$') < 0:
895 return strSubst
896
897 matchlist = ['$' + key, '${' + key + '}']
898 val = env.get(key, '')
899 def sub_match(match, val=val, matchlist=matchlist):
900 a = match.group(1)
901 if a in matchlist:
902 a = val
903 if is_Sequence(a):
904 return ' '.join(map(str, a))
905 else:
906 return str(a)
907
908 if is_Sequence(strSubst):
909 result = []
910 for arg in strSubst:
911 if is_String(arg):
912 if arg in matchlist:
913 arg = val
914 if is_Sequence(arg):
915 result.extend(arg)
916 else:
917 result.append(arg)
918 else:
919 result.append(_dollar_exps.sub(sub_match, arg))
920 else:
921 result.append(arg)
922 return result
923 elif is_String(strSubst):
924 return _dollar_exps.sub(sub_match, strSubst)
925 else:
926 return strSubst
927
928
929
930
931
932
933