Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1037)

Side by Side Diff: pylib/gyp/input.py

Issue 10448042: Add a few early exits to ExpandVariables. (Closed) Base URL: http://gyp.googlecode.com/svn/trunk/
Patch Set: Created 8 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright (c) 2012 Google Inc. All rights reserved. 1 # Copyright (c) 2012 Google Inc. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 from compiler.ast import Const 5 from compiler.ast import Const
6 from compiler.ast import Dict 6 from compiler.ast import Dict
7 from compiler.ast import Discard 7 from compiler.ast import Discard
8 from compiler.ast import List 8 from compiler.ast import List
9 from compiler.ast import Module 9 from compiler.ast import Module
10 from compiler.ast import Node 10 from compiler.ast import Node
(...skipping 519 matching lines...) Expand 10 before | Expand all | Expand 10 after
530 elif phase == PHASE_LATE: 530 elif phase == PHASE_LATE:
531 variable_re = late_variable_re 531 variable_re = late_variable_re
532 expansion_symbol = '>' 532 expansion_symbol = '>'
533 elif phase == PHASE_LATELATE: 533 elif phase == PHASE_LATELATE:
534 variable_re = latelate_variable_re 534 variable_re = latelate_variable_re
535 expansion_symbol = '^' 535 expansion_symbol = '^'
536 else: 536 else:
537 assert False 537 assert False
538 538
539 input_str = str(input) 539 input_str = str(input)
540 if IsStrCanonicalInt(input_str):
541 return int(input_str)
542
540 # Do a quick scan to determine if an expensive regex search is warranted. 543 # Do a quick scan to determine if an expensive regex search is warranted.
541 if expansion_symbol in input_str: 544 if expansion_symbol not in input_str:
542 # Get the entire list of matches as a list of MatchObject instances. 545 return input_str
543 # (using findall here would return strings instead of MatchObjects). 546
544 matches = [match for match in variable_re.finditer(input_str)] 547 # Get the entire list of matches as a list of MatchObject instances.
548 # (using findall here would return strings instead of MatchObjects).
549 matches = [match for match in variable_re.finditer(input_str)]
550 if not matches:
551 return input_str
552
553 output = input_str
554 # Reverse the list of matches so that replacements are done right-to-left.
Nico 2012/05/28 21:54:50 Rietveld doesn't show it well, but from here on ev
555 # That ensures that earlier replacements won't mess up the string in a
556 # way that causes later calls to find the earlier substituted text instead
557 # of what's intended for replacement.
558 matches.reverse()
559 for match_group in matches:
560 match = match_group.groupdict()
561 gyp.DebugOutput(gyp.DEBUG_VARIABLES,
562 "Matches: %s" % repr(match))
563 # match['replace'] is the substring to look for, match['type']
564 # is the character code for the replacement type (< > <! >! <| >| <@
565 # >@ <!@ >!@), match['is_array'] contains a '[' for command
566 # arrays, and match['content'] is the name of the variable (< >)
567 # or command to run (<! >!). match['command_string'] is an optional
568 # command string. Currently, only 'pymod_do_main' is supported.
569
570 # run_command is true if a ! variant is used.
571 run_command = '!' in match['type']
572 command_string = match['command_string']
573
574 # file_list is true if a | variant is used.
575 file_list = '|' in match['type']
576
577 # Capture these now so we can adjust them later.
578 replace_start = match_group.start('replace')
579 replace_end = match_group.end('replace')
580
581 # Find the ending paren, and re-evaluate the contained string.
582 (c_start, c_end) = FindEnclosingBracketGroup(input_str[replace_start:])
583
584 # Adjust the replacement range to match the entire command
585 # found by FindEnclosingBracketGroup (since the variable_re
586 # probably doesn't match the entire command if it contained
587 # nested variables).
588 replace_end = replace_start + c_end
589
590 # Find the "real" replacement, matching the appropriate closing
591 # paren, and adjust the replacement start and end.
592 replacement = input_str[replace_start:replace_end]
593
594 # Figure out what the contents of the variable parens are.
595 contents_start = replace_start + c_start + 1
596 contents_end = replace_end - 1
597 contents = input_str[contents_start:contents_end]
598
599 # Do filter substitution now for <|().
600 # Admittedly, this is different than the evaluation order in other
601 # contexts. However, since filtration has no chance to run on <|(),
602 # this seems like the only obvious way to give them access to filters.
603 if file_list:
604 processed_variables = copy.deepcopy(variables)
605 ProcessListFiltersInDict(contents, processed_variables)
606 # Recurse to expand variables in the contents
607 contents = ExpandVariables(contents, phase,
608 processed_variables, build_file)
609 else:
610 # Recurse to expand variables in the contents
611 contents = ExpandVariables(contents, phase, variables, build_file)
612
613 # Strip off leading/trailing whitespace so that variable matches are
614 # simpler below (and because they are rarely needed).
615 contents = contents.strip()
616
617 # expand_to_list is true if an @ variant is used. In that case,
618 # the expansion should result in a list. Note that the caller
619 # is to be expecting a list in return, and not all callers do
620 # because not all are working in list context. Also, for list
621 # expansions, there can be no other text besides the variable
622 # expansion in the input string.
623 expand_to_list = '@' in match['type'] and input_str == replacement
624
625 if run_command or file_list:
626 # Find the build file's directory, so commands can be run or file lists
627 # generated relative to it.
628 build_file_dir = os.path.dirname(build_file)
629 if build_file_dir == '':
630 # If build_file is just a leaf filename indicating a file in the
631 # current directory, build_file_dir might be an empty string. Set
632 # it to None to signal to subprocess.Popen that it should run the
633 # command in the current directory.
634 build_file_dir = None
635
636 # Support <|(listfile.txt ...) which generates a file
637 # containing items from a gyp list, generated at gyp time.
638 # This works around actions/rules which have more inputs than will
639 # fit on the command line.
640 if file_list:
641 if type(contents) == list:
642 contents_list = contents
643 else:
644 contents_list = contents.split(' ')
645 replacement = contents_list[0]
646 path = replacement
647 if not os.path.isabs(path):
648 path = os.path.join(build_file_dir, path)
649 f = gyp.common.WriteOnDiff(path)
650 for i in contents_list[1:]:
651 f.write('%s\n' % i)
652 f.close()
653
654 elif run_command:
655 use_shell = True
656 if match['is_array']:
657 contents = eval(contents)
658 use_shell = False
659
660 # Check for a cached value to avoid executing commands, or generating
661 # file lists more than once.
662 # TODO(http://code.google.com/p/gyp/issues/detail?id=112): It is
663 # possible that the command being invoked depends on the current
664 # directory. For that case the syntax needs to be extended so that the
665 # directory is also used in cache_key (it becomes a tuple).
666 # TODO(http://code.google.com/p/gyp/issues/detail?id=111): In theory,
667 # someone could author a set of GYP files where each time the command
668 # is invoked it produces different output by design. When the need
669 # arises, the syntax should be extended to support no caching off a
670 # command's output so it is run every time.
671 cache_key = str(contents)
672 cached_value = cached_command_results.get(cache_key, None)
673 if cached_value is None:
674 gyp.DebugOutput(gyp.DEBUG_VARIABLES,
675 "Executing command '%s' in directory '%s'" %
676 (contents,build_file_dir))
677
678 replacement = ''
679
680 if command_string == 'pymod_do_main':
681 # <!pymod_do_main(modulename param eters) loads |modulename| as a
682 # python module and then calls that module's DoMain() function,
683 # passing ["param", "eters"] as a single list argument. For modules
684 # that don't load quickly, this can be faster than
685 # <!(python modulename param eters). Do this in |build_file_dir|.
686 oldwd = os.getcwd() # Python doesn't like os.open('.'): no fchdir.
687 os.chdir(build_file_dir)
688
689 parsed_contents = shlex.split(contents)
690 py_module = __import__(parsed_contents[0])
691 replacement = str(py_module.DoMain(parsed_contents[1:])).rstrip()
692
693 os.chdir(oldwd)
694 assert replacement != None
695 elif command_string:
696 raise Exception("Unknown command string '%s' in '%s'." %
697 (command_string, contents))
698 else:
699 # Fix up command with platform specific workarounds.
700 contents = FixupPlatformCommand(contents)
701 p = subprocess.Popen(contents, shell=use_shell,
702 stdout=subprocess.PIPE,
703 stderr=subprocess.PIPE,
704 stdin=subprocess.PIPE,
705 cwd=build_file_dir)
706
707 p_stdout, p_stderr = p.communicate('')
708
709 if p.wait() != 0 or p_stderr:
710 sys.stderr.write(p_stderr)
711 # Simulate check_call behavior, since check_call only exists
712 # in python 2.5 and later.
713 raise Exception("Call to '%s' returned exit status %d." %
714 (contents, p.returncode))
715 replacement = p_stdout.rstrip()
716
717 cached_command_results[cache_key] = replacement
718 else:
719 gyp.DebugOutput(gyp.DEBUG_VARIABLES,
720 "Had cache value for command '%s' in directory '%s'" %
721 (contents,build_file_dir))
722 replacement = cached_value
723
724 else:
725 if not contents in variables:
726 if contents[-1] in ['!', '/']:
727 # In order to allow cross-compiles (nacl) to happen more naturally,
728 # we will allow references to >(sources/) etc. to resolve to
729 # and empty list if undefined. This allows actions to:
730 # 'action!': [
731 # '>@(_sources!)',
732 # ],
733 # 'action/': [
734 # '>@(_sources/)',
735 # ],
736 replacement = []
737 else:
738 raise KeyError, 'Undefined variable ' + contents + \
739 ' in ' + build_file
740 else:
741 replacement = variables[contents]
742
743 if isinstance(replacement, list):
744 for item in replacement:
745 if (not contents[-1] == '/' and
746 not isinstance(item, str) and not isinstance(item, int)):
747 raise TypeError, 'Variable ' + contents + \
748 ' must expand to a string or list of strings; ' + \
749 'list contains a ' + \
750 item.__class__.__name__
751 # Run through the list and handle variable expansions in it. Since
752 # the list is guaranteed not to contain dicts, this won't do anything
753 # with conditions sections.
754 ProcessVariablesAndConditionsInList(replacement, phase, variables,
755 build_file)
756 elif not isinstance(replacement, str) and \
757 not isinstance(replacement, int):
758 raise TypeError, 'Variable ' + contents + \
759 ' must expand to a string or list of strings; ' + \
760 'found a ' + replacement.__class__.__name__
761
762 if expand_to_list:
763 # Expanding in list context. It's guaranteed that there's only one
764 # replacement to do in |input_str| and that it's this replacement. See
765 # above.
766 if isinstance(replacement, list):
767 # If it's already a list, make a copy.
768 output = replacement[:]
769 else:
770 # Split it the same way sh would split arguments.
771 output = shlex.split(str(replacement))
772 else:
773 # Expanding in string context.
774 encoded_replacement = ''
775 if isinstance(replacement, list):
776 # When expanding a list into string context, turn the list items
777 # into a string in a way that will work with a subprocess call.
778 #
779 # TODO(mark): This isn't completely correct. This should
780 # call a generator-provided function that observes the
781 # proper list-to-argument quoting rules on a specific
782 # platform instead of just calling the POSIX encoding
783 # routine.
784 encoded_replacement = gyp.common.EncodePOSIXShellList(replacement)
785 else:
786 encoded_replacement = replacement
787
788 output = output[:replace_start] + str(encoded_replacement) + \
789 output[replace_end:]
790 # Prepare for the next match iteration.
791 input_str = output
792
793 # Look for more matches now that we've replaced some, to deal with
794 # expanding local variables (variables defined in the same
795 # variables block as this one).
796 gyp.DebugOutput(gyp.DEBUG_VARIABLES,
797 "Found output %s, recursing." % repr(output))
798 if isinstance(output, list):
799 if output and isinstance(output[0], list):
800 # Leave output alone if it's a list of lists.
801 # We don't want such lists to be stringified.
802 pass
803 else:
804 new_output = []
805 for item in output:
806 new_output.append(
807 ExpandVariables(item, phase, variables, build_file))
808 output = new_output
545 else: 809 else:
546 matches = None 810 output = ExpandVariables(output, phase, variables, build_file)
547
548 output = input_str
549 if matches:
550 # Reverse the list of matches so that replacements are done right-to-left.
551 # That ensures that earlier replacements won't mess up the string in a
552 # way that causes later calls to find the earlier substituted text instead
553 # of what's intended for replacement.
554 matches.reverse()
555 for match_group in matches:
556 match = match_group.groupdict()
557 gyp.DebugOutput(gyp.DEBUG_VARIABLES,
558 "Matches: %s" % repr(match))
559 # match['replace'] is the substring to look for, match['type']
560 # is the character code for the replacement type (< > <! >! <| >| <@
561 # >@ <!@ >!@), match['is_array'] contains a '[' for command
562 # arrays, and match['content'] is the name of the variable (< >)
563 # or command to run (<! >!). match['command_string'] is an optional
564 # command string. Currently, only 'pymod_do_main' is supported.
565
566 # run_command is true if a ! variant is used.
567 run_command = '!' in match['type']
568 command_string = match['command_string']
569
570 # file_list is true if a | variant is used.
571 file_list = '|' in match['type']
572
573 # Capture these now so we can adjust them later.
574 replace_start = match_group.start('replace')
575 replace_end = match_group.end('replace')
576
577 # Find the ending paren, and re-evaluate the contained string.
578 (c_start, c_end) = FindEnclosingBracketGroup(input_str[replace_start:])
579
580 # Adjust the replacement range to match the entire command
581 # found by FindEnclosingBracketGroup (since the variable_re
582 # probably doesn't match the entire command if it contained
583 # nested variables).
584 replace_end = replace_start + c_end
585
586 # Find the "real" replacement, matching the appropriate closing
587 # paren, and adjust the replacement start and end.
588 replacement = input_str[replace_start:replace_end]
589
590 # Figure out what the contents of the variable parens are.
591 contents_start = replace_start + c_start + 1
592 contents_end = replace_end - 1
593 contents = input_str[contents_start:contents_end]
594
595 # Do filter substitution now for <|().
596 # Admittedly, this is different than the evaluation order in other
597 # contexts. However, since filtration has no chance to run on <|(),
598 # this seems like the only obvious way to give them access to filters.
599 if file_list:
600 processed_variables = copy.deepcopy(variables)
601 ProcessListFiltersInDict(contents, processed_variables)
602 # Recurse to expand variables in the contents
603 contents = ExpandVariables(contents, phase,
604 processed_variables, build_file)
605 else:
606 # Recurse to expand variables in the contents
607 contents = ExpandVariables(contents, phase, variables, build_file)
608
609 # Strip off leading/trailing whitespace so that variable matches are
610 # simpler below (and because they are rarely needed).
611 contents = contents.strip()
612
613 # expand_to_list is true if an @ variant is used. In that case,
614 # the expansion should result in a list. Note that the caller
615 # is to be expecting a list in return, and not all callers do
616 # because not all are working in list context. Also, for list
617 # expansions, there can be no other text besides the variable
618 # expansion in the input string.
619 expand_to_list = '@' in match['type'] and input_str == replacement
620
621 if run_command or file_list:
622 # Find the build file's directory, so commands can be run or file lists
623 # generated relative to it.
624 build_file_dir = os.path.dirname(build_file)
625 if build_file_dir == '':
626 # If build_file is just a leaf filename indicating a file in the
627 # current directory, build_file_dir might be an empty string. Set
628 # it to None to signal to subprocess.Popen that it should run the
629 # command in the current directory.
630 build_file_dir = None
631
632 # Support <|(listfile.txt ...) which generates a file
633 # containing items from a gyp list, generated at gyp time.
634 # This works around actions/rules which have more inputs than will
635 # fit on the command line.
636 if file_list:
637 if type(contents) == list:
638 contents_list = contents
639 else:
640 contents_list = contents.split(' ')
641 replacement = contents_list[0]
642 path = replacement
643 if not os.path.isabs(path):
644 path = os.path.join(build_file_dir, path)
645 f = gyp.common.WriteOnDiff(path)
646 for i in contents_list[1:]:
647 f.write('%s\n' % i)
648 f.close()
649
650 elif run_command:
651 use_shell = True
652 if match['is_array']:
653 contents = eval(contents)
654 use_shell = False
655
656 # Check for a cached value to avoid executing commands, or generating
657 # file lists more than once.
658 # TODO(http://code.google.com/p/gyp/issues/detail?id=112): It is
659 # possible that the command being invoked depends on the current
660 # directory. For that case the syntax needs to be extended so that the
661 # directory is also used in cache_key (it becomes a tuple).
662 # TODO(http://code.google.com/p/gyp/issues/detail?id=111): In theory,
663 # someone could author a set of GYP files where each time the command
664 # is invoked it produces different output by design. When the need
665 # arises, the syntax should be extended to support no caching off a
666 # command's output so it is run every time.
667 cache_key = str(contents)
668 cached_value = cached_command_results.get(cache_key, None)
669 if cached_value is None:
670 gyp.DebugOutput(gyp.DEBUG_VARIABLES,
671 "Executing command '%s' in directory '%s'" %
672 (contents,build_file_dir))
673
674 replacement = ''
675
676 if command_string == 'pymod_do_main':
677 # <!pymod_do_main(modulename param eters) loads |modulename| as a
678 # python module and then calls that module's DoMain() function,
679 # passing ["param", "eters"] as a single list argument. For modules
680 # that don't load quickly, this can be faster than
681 # <!(python modulename param eters). Do this in |build_file_dir|.
682 oldwd = os.getcwd() # Python doesn't like os.open('.'): no fchdir.
683 os.chdir(build_file_dir)
684
685 parsed_contents = shlex.split(contents)
686 py_module = __import__(parsed_contents[0])
687 replacement = str(py_module.DoMain(parsed_contents[1:])).rstrip()
688
689 os.chdir(oldwd)
690 assert replacement != None
691 elif command_string:
692 raise Exception("Unknown command string '%s' in '%s'." %
693 (command_string, contents))
694 else:
695 # Fix up command with platform specific workarounds.
696 contents = FixupPlatformCommand(contents)
697 p = subprocess.Popen(contents, shell=use_shell,
698 stdout=subprocess.PIPE,
699 stderr=subprocess.PIPE,
700 stdin=subprocess.PIPE,
701 cwd=build_file_dir)
702
703 p_stdout, p_stderr = p.communicate('')
704
705 if p.wait() != 0 or p_stderr:
706 sys.stderr.write(p_stderr)
707 # Simulate check_call behavior, since check_call only exists
708 # in python 2.5 and later.
709 raise Exception("Call to '%s' returned exit status %d." %
710 (contents, p.returncode))
711 replacement = p_stdout.rstrip()
712
713 cached_command_results[cache_key] = replacement
714 else:
715 gyp.DebugOutput(gyp.DEBUG_VARIABLES,
716 "Had cache value for command '%s' in directory '%s'" %
717 (contents,build_file_dir))
718 replacement = cached_value
719
720 else:
721 if not contents in variables:
722 if contents[-1] in ['!', '/']:
723 # In order to allow cross-compiles (nacl) to happen more naturally,
724 # we will allow references to >(sources/) etc. to resolve to
725 # and empty list if undefined. This allows actions to:
726 # 'action!': [
727 # '>@(_sources!)',
728 # ],
729 # 'action/': [
730 # '>@(_sources/)',
731 # ],
732 replacement = []
733 else:
734 raise KeyError, 'Undefined variable ' + contents + \
735 ' in ' + build_file
736 else:
737 replacement = variables[contents]
738
739 if isinstance(replacement, list):
740 for item in replacement:
741 if (not contents[-1] == '/' and
742 not isinstance(item, str) and not isinstance(item, int)):
743 raise TypeError, 'Variable ' + contents + \
744 ' must expand to a string or list of strings; ' + \
745 'list contains a ' + \
746 item.__class__.__name__
747 # Run through the list and handle variable expansions in it. Since
748 # the list is guaranteed not to contain dicts, this won't do anything
749 # with conditions sections.
750 ProcessVariablesAndConditionsInList(replacement, phase, variables,
751 build_file)
752 elif not isinstance(replacement, str) and \
753 not isinstance(replacement, int):
754 raise TypeError, 'Variable ' + contents + \
755 ' must expand to a string or list of strings; ' + \
756 'found a ' + replacement.__class__.__name__
757
758 if expand_to_list:
759 # Expanding in list context. It's guaranteed that there's only one
760 # replacement to do in |input_str| and that it's this replacement. See
761 # above.
762 if isinstance(replacement, list):
763 # If it's already a list, make a copy.
764 output = replacement[:]
765 else:
766 # Split it the same way sh would split arguments.
767 output = shlex.split(str(replacement))
768 else:
769 # Expanding in string context.
770 encoded_replacement = ''
771 if isinstance(replacement, list):
772 # When expanding a list into string context, turn the list items
773 # into a string in a way that will work with a subprocess call.
774 #
775 # TODO(mark): This isn't completely correct. This should
776 # call a generator-provided function that observes the
777 # proper list-to-argument quoting rules on a specific
778 # platform instead of just calling the POSIX encoding
779 # routine.
780 encoded_replacement = gyp.common.EncodePOSIXShellList(replacement)
781 else:
782 encoded_replacement = replacement
783
784 output = output[:replace_start] + str(encoded_replacement) + \
785 output[replace_end:]
786 # Prepare for the next match iteration.
787 input_str = output
788
789 # Look for more matches now that we've replaced some, to deal with
790 # expanding local variables (variables defined in the same
791 # variables block as this one).
792 gyp.DebugOutput(gyp.DEBUG_VARIABLES,
793 "Found output %s, recursing." % repr(output))
794 if isinstance(output, list):
795 if output and isinstance(output[0], list):
796 # Leave output alone if it's a list of lists.
797 # We don't want such lists to be stringified.
798 pass
799 else:
800 new_output = []
801 for item in output:
802 new_output.append(
803 ExpandVariables(item, phase, variables, build_file))
804 output = new_output
805 else:
806 output = ExpandVariables(output, phase, variables, build_file)
807 811
808 # Convert all strings that are canonically-represented integers into integers. 812 # Convert all strings that are canonically-represented integers into integers.
809 if isinstance(output, list): 813 if isinstance(output, list):
810 for index in xrange(0, len(output)): 814 for index in xrange(0, len(output)):
811 if IsStrCanonicalInt(output[index]): 815 if IsStrCanonicalInt(output[index]):
812 output[index] = int(output[index]) 816 output[index] = int(output[index])
813 elif IsStrCanonicalInt(output): 817 elif IsStrCanonicalInt(output):
814 output = int(output) 818 output = int(output)
815 819
816 return output 820 return output
(...skipping 1657 matching lines...) Expand 10 before | Expand all | Expand 10 after
2474 ValidateRunAsInTarget(target, target_dict, build_file) 2478 ValidateRunAsInTarget(target, target_dict, build_file)
2475 ValidateActionsInTarget(target, target_dict, build_file) 2479 ValidateActionsInTarget(target, target_dict, build_file)
2476 2480
2477 # Generators might not expect ints. Turn them into strs. 2481 # Generators might not expect ints. Turn them into strs.
2478 TurnIntIntoStrInDict(data) 2482 TurnIntIntoStrInDict(data)
2479 2483
2480 # TODO(mark): Return |data| for now because the generator needs a list of 2484 # TODO(mark): Return |data| for now because the generator needs a list of
2481 # build files that came in. In the future, maybe it should just accept 2485 # build files that came in. In the future, maybe it should just accept
2482 # a list, and not the whole data dict. 2486 # a list, and not the whole data dict.
2483 return [flat_list, targets, data] 2487 return [flat_list, targets, data]
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698