1# MIT License 2# 3# Copyright The SCons Foundation 4# 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: 12# 13# The above copyright notice and this permission notice shall be included 14# in all copies or substantial portions of the Software. 15# 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. 23 24"""SCons string substitution.""" 25 26import collections 27import re 28from inspect import signature, Parameter 29 30import SCons.Errors 31from SCons.Util import is_String, is_Sequence 32 33# Indexed by the SUBST_* constants below. 34_strconv = [ 35 SCons.Util.to_String_for_subst, 36 SCons.Util.to_String_for_subst, 37 SCons.Util.to_String_for_signature, 38] 39 40AllowableExceptions = (IndexError, NameError) 41 42 43def SetAllowableExceptions(*excepts): 44 global AllowableExceptions 45 AllowableExceptions = [_f for _f in excepts if _f] 46 47 48def raise_exception(exception, target, s): 49 name = exception.__class__.__name__ 50 msg = "%s `%s' trying to evaluate `%s'" % (name, exception, s) 51 if target: 52 raise SCons.Errors.BuildError(target[0], msg) 53 else: 54 raise SCons.Errors.UserError(msg) 55 56 57class Literal: 58 """A wrapper for a string. If you use this object wrapped 59 around a string, then it will be interpreted as literal. 60 When passed to the command interpreter, all special 61 characters will be escaped.""" 62 def __init__(self, lstr): 63 self.lstr = lstr 64 65 def __str__(self): 66 return self.lstr 67 68 def escape(self, escape_func): 69 return escape_func(self.lstr) 70 71 def for_signature(self): 72 return self.lstr 73 74 def is_literal(self): 75 return 1 76 77 def __eq__(self, other): 78 if not isinstance(other, Literal): 79 return False 80 return self.lstr == other.lstr 81 82 def __neq__(self, other): 83 return not self.__eq__(other) 84 85 def __hash__(self): 86 return hash(self.lstr) 87 88class SpecialAttrWrapper: 89 """This is a wrapper for what we call a 'Node special attribute.' 90 This is any of the attributes of a Node that we can reference from 91 Environment variable substitution, such as $TARGET.abspath or 92 $SOURCES[1].filebase. We implement the same methods as Literal 93 so we can handle special characters, plus a for_signature method, 94 such that we can return some canonical string during signature 95 calculation to avoid unnecessary rebuilds.""" 96 97 def __init__(self, lstr, for_signature=None): 98 """The for_signature parameter, if supplied, will be the 99 canonical string we return from for_signature(). Else 100 we will simply return lstr.""" 101 self.lstr = lstr 102 if for_signature: 103 self.forsig = for_signature 104 else: 105 self.forsig = lstr 106 107 def __str__(self): 108 return self.lstr 109 110 def escape(self, escape_func): 111 return escape_func(self.lstr) 112 113 def for_signature(self): 114 return self.forsig 115 116 def is_literal(self): 117 return 1 118 119def quote_spaces(arg): 120 """Generic function for putting double quotes around any string that 121 has white space in it.""" 122 if ' ' in arg or '\t' in arg: 123 return '"%s"' % arg 124 else: 125 return str(arg) 126 127class CmdStringHolder(collections.UserString): 128 """This is a special class used to hold strings generated by 129 scons_subst() and scons_subst_list(). It defines a special method 130 escape(). When passed a function with an escape algorithm for a 131 particular platform, it will return the contained string with the 132 proper escape sequences inserted. 133 """ 134 def __init__(self, cmd, literal=None): 135 collections.UserString.__init__(self, cmd) 136 self.literal = literal 137 138 def is_literal(self): 139 return self.literal 140 141 def escape(self, escape_func, quote_func=quote_spaces): 142 """Escape the string with the supplied function. The 143 function is expected to take an arbitrary string, then 144 return it with all special characters escaped and ready 145 for passing to the command interpreter. 146 147 After calling this function, the next call to str() will 148 return the escaped string. 149 """ 150 151 if self.is_literal(): 152 return escape_func(self.data) 153 elif ' ' in self.data or '\t' in self.data: 154 return quote_func(self.data) 155 else: 156 return self.data 157 158def escape_list(mylist, escape_func): 159 """Escape a list of arguments by running the specified escape_func 160 on every object in the list that has an escape() method.""" 161 def escape(obj, escape_func=escape_func): 162 try: 163 e = obj.escape 164 except AttributeError: 165 return obj 166 else: 167 return e(escape_func) 168 return list(map(escape, mylist)) 169 170class NLWrapper: 171 """A wrapper class that delays turning a list of sources or targets 172 into a NodeList until it's needed. The specified function supplied 173 when the object is initialized is responsible for turning raw nodes 174 into proxies that implement the special attributes like .abspath, 175 .source, etc. This way, we avoid creating those proxies just 176 "in case" someone is going to use $TARGET or the like, and only 177 go through the trouble if we really have to. 178 179 In practice, this might be a wash performance-wise, but it's a little 180 cleaner conceptually... 181 """ 182 183 def __init__(self, list, func): 184 self.list = list 185 self.func = func 186 def _return_nodelist(self): 187 return self.nodelist 188 def _gen_nodelist(self): 189 mylist = self.list 190 if mylist is None: 191 mylist = [] 192 elif not is_Sequence(mylist): 193 mylist = [mylist] 194 # The map(self.func) call is what actually turns 195 # a list into appropriate proxies. 196 self.nodelist = SCons.Util.NodeList(list(map(self.func, mylist))) 197 self._create_nodelist = self._return_nodelist 198 return self.nodelist 199 _create_nodelist = _gen_nodelist 200 201 202class Targets_or_Sources(collections.UserList): 203 """A class that implements $TARGETS or $SOURCES expansions by in turn 204 wrapping a NLWrapper. This class handles the different methods used 205 to access the list, calling the NLWrapper to create proxies on demand. 206 207 Note that we subclass collections.UserList purely so that the 208 is_Sequence() function will identify an object of this class as 209 a list during variable expansion. We're not really using any 210 collections.UserList methods in practice. 211 """ 212 def __init__(self, nl): 213 self.nl = nl 214 def __getattr__(self, attr): 215 nl = self.nl._create_nodelist() 216 return getattr(nl, attr) 217 def __getitem__(self, i): 218 nl = self.nl._create_nodelist() 219 return nl[i] 220 def __str__(self): 221 nl = self.nl._create_nodelist() 222 return str(nl) 223 def __repr__(self): 224 nl = self.nl._create_nodelist() 225 return repr(nl) 226 227class Target_or_Source: 228 """A class that implements $TARGET or $SOURCE expansions by in turn 229 wrapping a NLWrapper. This class handles the different methods used 230 to access an individual proxy Node, calling the NLWrapper to create 231 a proxy on demand. 232 """ 233 def __init__(self, nl): 234 self.nl = nl 235 def __getattr__(self, attr): 236 nl = self.nl._create_nodelist() 237 try: 238 nl0 = nl[0] 239 except IndexError: 240 # If there is nothing in the list, then we have no attributes to 241 # pass through, so raise AttributeError for everything. 242 raise AttributeError("NodeList has no attribute: %s" % attr) 243 return getattr(nl0, attr) 244 def __str__(self): 245 nl = self.nl._create_nodelist() 246 if nl: 247 return str(nl[0]) 248 return '' 249 def __repr__(self): 250 nl = self.nl._create_nodelist() 251 if nl: 252 return repr(nl[0]) 253 return '' 254 255class NullNodeList(SCons.Util.NullSeq): 256 def __call__(self, *args, **kwargs): return '' 257 def __str__(self): return '' 258 259NullNodesList = NullNodeList() 260 261def subst_dict(target, source): 262 """Create a dictionary for substitution of special 263 construction variables. 264 265 This translates the following special arguments: 266 267 target - the target (object or array of objects), 268 used to generate the TARGET and TARGETS 269 construction variables 270 271 source - the source (object or array of objects), 272 used to generate the SOURCES and SOURCE 273 construction variables 274 """ 275 dict = {} 276 277 if target: 278 def get_tgt_subst_proxy(thing): 279 try: 280 subst_proxy = thing.get_subst_proxy() 281 except AttributeError: 282 subst_proxy = thing # probably a string, just return it 283 return subst_proxy 284 tnl = NLWrapper(target, get_tgt_subst_proxy) 285 dict['TARGETS'] = Targets_or_Sources(tnl) 286 dict['TARGET'] = Target_or_Source(tnl) 287 288 # This is a total cheat, but hopefully this dictionary goes 289 # away soon anyway. We just let these expand to $TARGETS 290 # because that's "good enough" for the use of ToolSurrogates 291 # (see test/ToolSurrogate.py) to generate documentation. 292 dict['CHANGED_TARGETS'] = '$TARGETS' 293 dict['UNCHANGED_TARGETS'] = '$TARGETS' 294 else: 295 dict['TARGETS'] = NullNodesList 296 dict['TARGET'] = NullNodesList 297 298 if source: 299 def get_src_subst_proxy(node): 300 try: 301 rfile = node.rfile 302 except AttributeError: 303 pass 304 else: 305 node = rfile() 306 try: 307 return node.get_subst_proxy() 308 except AttributeError: 309 return node # probably a String, just return it 310 snl = NLWrapper(source, get_src_subst_proxy) 311 dict['SOURCES'] = Targets_or_Sources(snl) 312 dict['SOURCE'] = Target_or_Source(snl) 313 314 # This is a total cheat, but hopefully this dictionary goes 315 # away soon anyway. We just let these expand to $TARGETS 316 # because that's "good enough" for the use of ToolSurrogates 317 # (see test/ToolSurrogate.py) to generate documentation. 318 dict['CHANGED_SOURCES'] = '$SOURCES' 319 dict['UNCHANGED_SOURCES'] = '$SOURCES' 320 else: 321 dict['SOURCES'] = NullNodesList 322 dict['SOURCE'] = NullNodesList 323 324 return dict 325 326 327_callable_args_set = {'target', 'source', 'env', 'for_signature'} 328 329class StringSubber: 330 """A class to construct the results of a scons_subst() call. 331 332 This binds a specific construction environment, mode, target and 333 source with two methods (substitute() and expand()) that handle 334 the expansion. 335 """ 336 337 338 def __init__(self, env, mode, conv, gvars): 339 self.env = env 340 self.mode = mode 341 self.conv = conv 342 self.gvars = gvars 343 344 def expand(self, s, lvars): 345 """Expand a single "token" as necessary, returning an 346 appropriate string containing the expansion. 347 348 This handles expanding different types of things (strings, 349 lists, callables) appropriately. It calls the wrapper 350 substitute() method to re-expand things as necessary, so that 351 the results of expansions of side-by-side strings still get 352 re-evaluated separately, not smushed together. 353 """ 354 if is_String(s): 355 try: 356 s0, s1 = s[:2] 357 except (IndexError, ValueError): 358 return s 359 if s0 != '$': 360 return s 361 if s1 == '$': 362 # In this case keep the double $'s which we'll later 363 # swap for a single dollar sign as we need to retain 364 # this information to properly avoid matching "$("" when 365 # the actual text was "$$("" (or "$)"" when "$$)"" ) 366 return '$$' 367 elif s1 in '()': 368 return s 369 else: 370 key = s[1:] 371 if key[0] == '{' or '.' in key: 372 if key[0] == '{': 373 key = key[1:-1] 374 375 # Store for error messages if we fail to expand the 376 # value 377 old_s = s 378 s = None 379 if key in lvars: 380 s = lvars[key] 381 elif key in self.gvars: 382 s = self.gvars[key] 383 else: 384 try: 385 s = eval(key, self.gvars, lvars) 386 except KeyboardInterrupt: 387 raise 388 except Exception as e: 389 if e.__class__ in AllowableExceptions: 390 return '' 391 raise_exception(e, lvars['TARGETS'], old_s) 392 393 if s is None and NameError not in AllowableExceptions: 394 raise_exception(NameError(key), lvars['TARGETS'], old_s) 395 elif s is None: 396 return '' 397 398 # Before re-expanding the result, handle 399 # recursive expansion by copying the local 400 # variable dictionary and overwriting a null 401 # string for the value of the variable name 402 # we just expanded. 403 # 404 # This could potentially be optimized by only 405 # copying lvars when s contains more expansions, 406 # but lvars is usually supposed to be pretty 407 # small, and deeply nested variable expansions 408 # are probably more the exception than the norm, 409 # so it should be tolerable for now. 410 lv = lvars.copy() 411 var = key.split('.')[0] 412 lv[var] = '' 413 return self.substitute(s, lv) 414 elif is_Sequence(s): 415 def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars): 416 return conv(substitute(l, lvars)) 417 return list(map(func, s)) 418 elif callable(s): 419 # SCons has the unusual Null class where any __getattr__ call returns it's self, 420 # which does not work the signature module, and the Null class returns an empty 421 # string if called on, so we make an exception in this condition for Null class 422 # Also allow callables where the only non default valued args match the expected defaults 423 # this should also allow functools.partial's to work. 424 if isinstance(s, SCons.Util.Null) or {k for k, v in signature(s).parameters.items() if 425 k in _callable_args_set or v.default == Parameter.empty} == _callable_args_set: 426 427 s = s(target=lvars['TARGETS'], 428 source=lvars['SOURCES'], 429 env=self.env, 430 for_signature=(self.mode != SUBST_CMD)) 431 else: 432 # This probably indicates that it's a callable 433 # object that doesn't match our calling arguments 434 # (like an Action). 435 if self.mode == SUBST_RAW: 436 return s 437 s = self.conv(s) 438 return self.substitute(s, lvars) 439 elif s is None: 440 return '' 441 else: 442 return s 443 444 def substitute(self, args, lvars): 445 """Substitute expansions in an argument or list of arguments. 446 447 This serves as a wrapper for splitting up a string into 448 separate tokens. 449 """ 450 if is_String(args) and not isinstance(args, CmdStringHolder): 451 args = str(args) # In case it's a UserString. 452 try: 453 def sub_match(match): 454 return self.conv(self.expand(match.group(1), lvars)) 455 result = _dollar_exps.sub(sub_match, args) 456 except TypeError: 457 # If the internal conversion routine doesn't return 458 # strings (it could be overridden to return Nodes, for 459 # example), then the 1.5.2 re module will throw this 460 # exception. Back off to a slower, general-purpose 461 # algorithm that works for all data types. 462 args = _separate_args.findall(args) 463 result = [] 464 for a in args: 465 result.append(self.conv(self.expand(a, lvars))) 466 if len(result) == 1: 467 result = result[0] 468 else: 469 result = ''.join(map(str, result)) 470 return result 471 else: 472 return self.expand(args, lvars) 473 474 475class ListSubber(collections.UserList): 476 """A class to construct the results of a scons_subst_list() call. 477 478 Like StringSubber, this class binds a specific construction 479 environment, mode, target and source with two methods 480 (substitute() and expand()) that handle the expansion. 481 482 In addition, however, this class is used to track the state of 483 the result(s) we're gathering so we can do the appropriate thing 484 whenever we have to append another word to the result--start a new 485 line, start a new word, append to the current word, etc. We do 486 this by setting the "append" attribute to the right method so 487 that our wrapper methods only need ever call ListSubber.append(), 488 and the rest of the object takes care of doing the right thing 489 internally. 490 """ 491 def __init__(self, env, mode, conv, gvars): 492 collections.UserList.__init__(self, []) 493 self.env = env 494 self.mode = mode 495 self.conv = conv 496 self.gvars = gvars 497 498 if self.mode == SUBST_RAW: 499 self.add_strip = lambda x: self.append(x) 500 else: 501 self.add_strip = lambda x: None 502 self.in_strip = None 503 self.next_line() 504 505 def expanded(self, s): 506 """Determines if the string s requires further expansion. 507 508 Due to the implementation of ListSubber expand will call 509 itself 2 additional times for an already expanded string. This 510 method is used to determine if a string is already fully 511 expanded and if so exit the loop early to prevent these 512 recursive calls. 513 """ 514 if not is_String(s) or isinstance(s, CmdStringHolder): 515 return False 516 517 s = str(s) # in case it's a UserString 518 return _separate_args.findall(s) is None 519 520 def expand(self, s, lvars, within_list): 521 """Expand a single "token" as necessary, appending the 522 expansion to the current result. 523 524 This handles expanding different types of things (strings, 525 lists, callables) appropriately. It calls the wrapper 526 substitute() method to re-expand things as necessary, so that 527 the results of expansions of side-by-side strings still get 528 re-evaluated separately, not smushed together. 529 """ 530 531 if is_String(s): 532 try: 533 s0, s1 = s[:2] 534 except (IndexError, ValueError): 535 self.append(s) 536 return 537 if s0 != '$': 538 self.append(s) 539 return 540 if s1 == '$': 541 self.append('$') 542 elif s1 == '(': 543 self.open_strip('$(') 544 elif s1 == ')': 545 self.close_strip('$)') 546 else: 547 key = s[1:] 548 if key[0] == '{' or key.find('.') >= 0: 549 if key[0] == '{': 550 key = key[1:-1] 551 552 # Store for error messages if we fail to expand the 553 # value 554 old_s = s 555 s = None 556 if key in lvars: 557 s = lvars[key] 558 elif key in self.gvars: 559 s = self.gvars[key] 560 else: 561 try: 562 s = eval(key, self.gvars, lvars) 563 except KeyboardInterrupt: 564 raise 565 except Exception as e: 566 if e.__class__ in AllowableExceptions: 567 return 568 raise_exception(e, lvars['TARGETS'], old_s) 569 570 if s is None and NameError not in AllowableExceptions: 571 raise_exception(NameError(), lvars['TARGETS'], old_s) 572 elif s is None: 573 return 574 575 # If the string is already full expanded there's no 576 # need to continue recursion. 577 if self.expanded(s): 578 self.append(s) 579 return 580 581 # Before re-expanding the result, handle 582 # recursive expansion by copying the local 583 # variable dictionary and overwriting a null 584 # string for the value of the variable name 585 # we just expanded. 586 lv = lvars.copy() 587 var = key.split('.')[0] 588 lv[var] = '' 589 self.substitute(s, lv, 0) 590 self.this_word() 591 elif is_Sequence(s): 592 for a in s: 593 self.substitute(a, lvars, 1) 594 self.next_word() 595 elif callable(s): 596 # SCons has the unusual Null class where any __getattr__ call returns it's self, 597 # which does not work the signature module, and the Null class returns an empty 598 # string if called on, so we make an exception in this condition for Null class 599 # Also allow callables where the only non default valued args match the expected defaults 600 # this should also allow functools.partial's to work. 601 if isinstance(s, SCons.Util.Null) or {k for k, v in signature(s).parameters.items() if 602 k in _callable_args_set or v.default == Parameter.empty} == _callable_args_set: 603 604 s = s(target=lvars['TARGETS'], 605 source=lvars['SOURCES'], 606 env=self.env, 607 for_signature=(self.mode != SUBST_CMD)) 608 else: 609 # This probably indicates that it's a callable 610 # object that doesn't match our calling arguments 611 # (like an Action). 612 if self.mode == SUBST_RAW: 613 self.append(s) 614 return 615 s = self.conv(s) 616 self.substitute(s, lvars, within_list) 617 elif s is None: 618 self.this_word() 619 else: 620 self.append(s) 621 622 def substitute(self, args, lvars, within_list): 623 """Substitute expansions in an argument or list of arguments. 624 625 This serves as a wrapper for splitting up a string into 626 separate tokens. 627 """ 628 629 if is_String(args) and not isinstance(args, CmdStringHolder): 630 args = str(args) # In case it's a UserString. 631 args = _separate_args.findall(args) 632 for a in args: 633 if a[0] in ' \t\n\r\f\v': 634 if '\n' in a: 635 self.next_line() 636 elif within_list: 637 self.append(a) 638 else: 639 self.next_word() 640 else: 641 self.expand(a, lvars, within_list) 642 else: 643 self.expand(args, lvars, within_list) 644 645 def next_line(self): 646 """Arrange for the next word to start a new line. This 647 is like starting a new word, except that we have to append 648 another line to the result.""" 649 collections.UserList.append(self, []) 650 self.next_word() 651 652 def this_word(self): 653 """Arrange for the next word to append to the end of the 654 current last word in the result.""" 655 self.append = self.add_to_current_word 656 657 def next_word(self): 658 """Arrange for the next word to start a new word.""" 659 self.append = self.add_new_word 660 661 def add_to_current_word(self, x): 662 """Append the string x to the end of the current last word 663 in the result. If that is not possible, then just add 664 it as a new word. Make sure the entire concatenated string 665 inherits the object attributes of x (in particular, the 666 escape function) by wrapping it as CmdStringHolder.""" 667 668 if not self.in_strip or self.mode != SUBST_SIG: 669 try: 670 current_word = self[-1][-1] 671 except IndexError: 672 self.add_new_word(x) 673 else: 674 # All right, this is a hack and it should probably 675 # be refactored out of existence in the future. 676 # The issue is that we want to smoosh words together 677 # and make one file name that gets escaped if 678 # we're expanding something like foo$EXTENSION, 679 # but we don't want to smoosh them together if 680 # it's something like >$TARGET, because then we'll 681 # treat the '>' like it's part of the file name. 682 # So for now, just hard-code looking for the special 683 # command-line redirection characters... 684 try: 685 last_char = str(current_word)[-1] 686 except IndexError: 687 last_char = '\0' 688 if last_char in '<>|': 689 self.add_new_word(x) 690 else: 691 y = current_word + x 692 693 # We used to treat a word appended to a literal 694 # as a literal itself, but this caused problems 695 # with interpreting quotes around space-separated 696 # targets on command lines. Removing this makes 697 # none of the "substantive" end-to-end tests fail, 698 # so we'll take this out but leave it commented 699 # for now in case there's a problem not covered 700 # by the test cases and we need to resurrect this. 701 #literal1 = self.literal(self[-1][-1]) 702 #literal2 = self.literal(x) 703 y = self.conv(y) 704 if is_String(y): 705 #y = CmdStringHolder(y, literal1 or literal2) 706 y = CmdStringHolder(y, None) 707 self[-1][-1] = y 708 709 def add_new_word(self, x): 710 if not self.in_strip or self.mode != SUBST_SIG: 711 literal = self.literal(x) 712 x = self.conv(x) 713 if is_String(x): 714 x = CmdStringHolder(x, literal) 715 self[-1].append(x) 716 self.append = self.add_to_current_word 717 718 def literal(self, x): 719 try: 720 l = x.is_literal 721 except AttributeError: 722 return None 723 else: 724 return l() 725 726 def open_strip(self, x): 727 """Handle the "open strip" $( token.""" 728 self.add_strip(x) 729 self.in_strip = 1 730 731 def close_strip(self, x): 732 """Handle the "close strip" $) token.""" 733 self.add_strip(x) 734 self.in_strip = None 735 736 737# Constants for the "mode" parameter to scons_subst_list() and 738# scons_subst(). SUBST_RAW gives the raw command line. SUBST_CMD 739# gives a command line suitable for passing to a shell. SUBST_SIG 740# gives a command line appropriate for calculating the signature 741# of a command line...if this changes, we should rebuild. 742SUBST_CMD = 0 743SUBST_RAW = 1 744SUBST_SIG = 2 745 746_rm = re.compile(r'\$[()]') 747 748# Note the pattern below only matches $( or $) when there is no 749# preceeding $. (Thus the (?<!\$)) 750_rm_split = re.compile(r'(?<!\$)(\$[()])') 751 752# Indexed by the SUBST_* constants above. 753_regex_remove = [ _rm, None, _rm_split ] 754 755def _rm_list(list): 756 return [l for l in list if l not in ('$(', '$)')] 757 758def _remove_list(list): 759 result = [] 760 depth = 0 761 for l in list: 762 if l == '$(': 763 depth += 1 764 elif l == '$)': 765 depth -= 1 766 if depth < 0: 767 break 768 elif depth == 0: 769 result.append(l) 770 if depth != 0: 771 return None 772 return result 773 774# Indexed by the SUBST_* constants above. 775_list_remove = [ _rm_list, None, _remove_list ] 776 777# Regular expressions for splitting strings and handling substitutions, 778# for use by the scons_subst() and scons_subst_list() functions: 779# 780# The first expression compiled matches all of the $-introduced tokens 781# that we need to process in some way, and is used for substitutions. 782# The expressions it matches are: 783# 784# "$$" 785# "$(" 786# "$)" 787# "$variable" [must begin with alphabetic or underscore] 788# "${any stuff}" 789# 790# The second expression compiled is used for splitting strings into tokens 791# to be processed, and it matches all of the tokens listed above, plus 792# the following that affect how arguments do or don't get joined together: 793# 794# " " [white space] 795# "non-white-space" [without any dollar signs] 796# "$" [single dollar sign] 797# 798_dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}' 799_dollar_exps = re.compile(r'(%s)' % _dollar_exps_str) 800_separate_args = re.compile(r'(%s|\s+|[^\s$]+|\$)' % _dollar_exps_str) 801 802# This regular expression is used to replace strings of multiple white 803# space characters in the string result from the scons_subst() function. 804_space_sep = re.compile(r'[\t ]+(?![^{]*})') 805 806def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None): 807 """Expand a string or list containing construction variable 808 substitutions. 809 810 This is the work-horse function for substitutions in file names 811 and the like. The companion scons_subst_list() function (below) 812 handles separating command lines into lists of arguments, so see 813 that function if that's what you're looking for. 814 """ 815 if (isinstance(strSubst, str) and '$' not in strSubst) or isinstance(strSubst, CmdStringHolder): 816 return strSubst 817 818 if conv is None: 819 conv = _strconv[mode] 820 821 # Doing this every time is a bit of a waste, since the Executor 822 # has typically already populated the OverrideEnvironment with 823 # $TARGET/$SOURCE variables. We're keeping this (for now), though, 824 # because it supports existing behavior that allows us to call 825 # an Action directly with an arbitrary target+source pair, which 826 # we use in Tool/tex.py to handle calling $BIBTEX when necessary. 827 # If we dropped that behavior (or found another way to cover it), 828 # we could get rid of this call completely and just rely on the 829 # Executor setting the variables. 830 if 'TARGET' not in lvars: 831 d = subst_dict(target, source) 832 if d: 833 lvars = lvars.copy() 834 lvars.update(d) 835 836 # We're (most likely) going to eval() things. If Python doesn't 837 # find a __builtins__ value in the global dictionary used for eval(), 838 # it copies the current global values for you. Avoid this by 839 # setting it explicitly and then deleting, so we don't pollute the 840 # construction environment Dictionary(ies) that are typically used 841 # for expansion. 842 gvars['__builtins__'] = __builtins__ 843 844 ss = StringSubber(env, mode, conv, gvars) 845 result = ss.substitute(strSubst, lvars) 846 847 try: 848 del gvars['__builtins__'] 849 except KeyError: 850 pass 851 852 res = result 853 if is_String(result): 854 # Remove $(-$) pairs and any stuff in between, 855 # if that's appropriate. 856 remove = _regex_remove[mode] 857 if remove: 858 if mode == SUBST_SIG: 859 result = _list_remove[mode](remove.split(result)) 860 if result is None: 861 raise SCons.Errors.UserError("Unbalanced $(/$) in: " + res) 862 result = ' '.join(result) 863 else: 864 result = remove.sub('', result) 865 if mode != SUBST_RAW: 866 # Compress strings of white space characters into 867 # a single space. 868 result = _space_sep.sub(' ', result).strip() 869 870 # Now replace escaped $'s currently "$$" 871 # This is needed because we now retain $$ instead of 872 # replacing them during substition to avoid 873 # improperly trying to escape "$$(" as being "$(" 874 result = result.replace('$$','$') 875 elif is_Sequence(result): 876 remove = _list_remove[mode] 877 if remove: 878 result = remove(result) 879 if result is None: 880 raise SCons.Errors.UserError("Unbalanced $(/$) in: " + str(res)) 881 882 return result 883 884def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None): 885 """Substitute construction variables in a string (or list or other 886 object) and separate the arguments into a command list. 887 888 The companion scons_subst() function (above) handles basic 889 substitutions within strings, so see that function instead 890 if that's what you're looking for. 891 """ 892 if conv is None: 893 conv = _strconv[mode] 894 895 # Doing this every time is a bit of a waste, since the Executor 896 # has typically already populated the OverrideEnvironment with 897 # $TARGET/$SOURCE variables. We're keeping this (for now), though, 898 # because it supports existing behavior that allows us to call 899 # an Action directly with an arbitrary target+source pair, which 900 # we use in Tool/tex.py to handle calling $BIBTEX when necessary. 901 # If we dropped that behavior (or found another way to cover it), 902 # we could get rid of this call completely and just rely on the 903 # Executor setting the variables. 904 if 'TARGET' not in lvars: 905 d = subst_dict(target, source) 906 if d: 907 lvars = lvars.copy() 908 lvars.update(d) 909 910 # We're (most likely) going to eval() things. If Python doesn't 911 # find a __builtins__ value in the global dictionary used for eval(), 912 # it copies the current global values for you. Avoid this by 913 # setting it explicitly and then deleting, so we don't pollute the 914 # construction environment Dictionary(ies) that are typically used 915 # for expansion. 916 gvars['__builtins__'] = __builtins__ 917 918 ls = ListSubber(env, mode, conv, gvars) 919 ls.substitute(strSubst, lvars, 0) 920 921 try: 922 del gvars['__builtins__'] 923 except KeyError: 924 pass 925 926 return ls.data 927 928def scons_subst_once(strSubst, env, key): 929 """Perform single (non-recursive) substitution of a single 930 construction variable keyword. 931 932 This is used when setting a variable when copying or overriding values 933 in an Environment. We want to capture (expand) the old value before 934 we override it, so people can do things like: 935 936 env2 = env.Clone(CCFLAGS = '$CCFLAGS -g') 937 938 We do this with some straightforward, brute-force code here... 939 """ 940 if isinstance(strSubst, str) and strSubst.find('$') < 0: 941 return strSubst 942 943 matchlist = ['$' + key, '${' + key + '}'] 944 val = env.get(key, '') 945 def sub_match(match, val=val, matchlist=matchlist): 946 a = match.group(1) 947 if a in matchlist: 948 a = val 949 if is_Sequence(a): 950 return ' '.join(map(str, a)) 951 else: 952 return str(a) 953 954 if is_Sequence(strSubst): 955 result = [] 956 for arg in strSubst: 957 if is_String(arg): 958 if arg in matchlist: 959 arg = val 960 if is_Sequence(arg): 961 result.extend(arg) 962 else: 963 result.append(arg) 964 else: 965 result.append(_dollar_exps.sub(sub_match, arg)) 966 else: 967 result.append(arg) 968 return result 969 elif is_String(strSubst): 970 return _dollar_exps.sub(sub_match, strSubst) 971 else: 972 return strSubst 973 974# Local Variables: 975# tab-width:4 976# indent-tabs-mode:nil 977# End: 978# vim: set expandtab tabstop=4 shiftwidth=4: 979