OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Enables directory-specific presubmit checks to run at upload and/or commit. | 6 """Enables directory-specific presubmit checks to run at upload and/or commit. |
7 """ | 7 """ |
8 | 8 |
9 __version__ = '1.3.2' | 9 __version__ = '1.3.2' |
10 | 10 |
(...skipping 486 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
497 | 497 |
498 Instance members: | 498 Instance members: |
499 tags: Dictionnary of KEY=VALUE pairs found in the change description. | 499 tags: Dictionnary of KEY=VALUE pairs found in the change description. |
500 self.KEY: equivalent to tags['KEY'] | 500 self.KEY: equivalent to tags['KEY'] |
501 """ | 501 """ |
502 | 502 |
503 # Matches key/value (or "tag") lines in changelist descriptions. | 503 # Matches key/value (or "tag") lines in changelist descriptions. |
504 _tag_line_re = re.compile( | 504 _tag_line_re = re.compile( |
505 '^\s*(?P<key>[A-Z][A-Z_0-9]*)\s*=\s*(?P<value>.*?)\s*$') | 505 '^\s*(?P<key>[A-Z][A-Z_0-9]*)\s*=\s*(?P<value>.*?)\s*$') |
506 | 506 |
507 def __init__(self, change_info, repository_root=''): | 507 def __init__(self, change_info): |
508 # Do not keep a reference to the original change_info. | 508 # Do not keep a reference to the original change_info. |
509 self._name = change_info.name | 509 self._name = change_info.name |
510 self._full_description = change_info.description | 510 self._full_description = change_info.description |
511 self._repository_root = repository_root | 511 self._repository_root = change_info.GetLocalRoot() |
512 self.issue = change_info.issue | 512 self.issue = change_info.issue |
513 self.patchset = change_info.patchset | 513 self.patchset = change_info.patchset |
514 | 514 |
515 # From the description text, build up a dictionary of key/value pairs | 515 # From the description text, build up a dictionary of key/value pairs |
516 # plus the description minus all key/value or "tag" lines. | 516 # plus the description minus all key/value or "tag" lines. |
517 self._description_without_tags = [] | 517 self._description_without_tags = [] |
518 self.tags = {} | 518 self.tags = {} |
519 for line in change_info.description.splitlines(): | 519 for line in change_info.description.splitlines(): |
520 m = self._tag_line_re.match(line) | 520 m = self._tag_line_re.match(line) |
521 if m: | 521 if m: |
522 self.tags[m.group('key')] = m.group('value') | 522 self.tags[m.group('key')] = m.group('value') |
523 else: | 523 else: |
524 self._description_without_tags.append(line) | 524 self._description_without_tags.append(line) |
525 | 525 |
526 # Change back to text and remove whitespace at end. | 526 # Change back to text and remove whitespace at end. |
527 self._description_without_tags = '\n'.join(self._description_without_tags) | 527 self._description_without_tags = '\n'.join(self._description_without_tags) |
528 self._description_without_tags = self._description_without_tags.rstrip() | 528 self._description_without_tags = self._description_without_tags.rstrip() |
529 | 529 |
530 self._affected_files = [ | 530 self._affected_files = [ |
531 SvnAffectedFile(info[1], info[0].strip(), repository_root) | 531 SvnAffectedFile(info[1], info[0].strip(), self._repository_root) |
532 for info in change_info.files | 532 for info in change_info.GetFiles() |
533 ] | 533 ] |
534 | 534 |
535 def Name(self): | 535 def Name(self): |
536 """Returns the change name.""" | 536 """Returns the change name.""" |
537 return self._name | 537 return self._name |
538 | 538 |
539 def DescriptionText(self): | 539 def DescriptionText(self): |
540 """Returns the user-entered changelist description, minus tags. | 540 """Returns the user-entered changelist description, minus tags. |
541 | 541 |
542 Any line in the user-provided description starting with e.g. "FOO=" | 542 Any line in the user-provided description starting with e.g. "FOO=" |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
644 break | 644 break |
645 entries.sort() | 645 entries.sort() |
646 entries = map(lambda x: os.path.join(x, 'PRESUBMIT.py'), entries) | 646 entries = map(lambda x: os.path.join(x, 'PRESUBMIT.py'), entries) |
647 return filter(lambda x: os.path.isfile(x), entries) | 647 return filter(lambda x: os.path.isfile(x), entries) |
648 | 648 |
649 | 649 |
650 class PresubmitExecuter(object): | 650 class PresubmitExecuter(object): |
651 def __init__(self, change_info, committing): | 651 def __init__(self, change_info, committing): |
652 """ | 652 """ |
653 Args: | 653 Args: |
654 change_info: The ChangeInfo object for the change. | 654 change_info: The gcl.ChangeInfo object for the change. |
655 committing: True if 'gcl commit' is running, False if 'gcl upload' is. | 655 committing: True if 'gcl commit' is running, False if 'gcl upload' is. |
656 """ | 656 """ |
657 # TODO(maruel): Determine the SCM. | 657 # TODO(maruel): Determine the SCM. |
658 self.change = GclChange(change_info, gcl.GetRepositoryRoot()) | 658 self.change = GclChange(change_info) |
659 self.committing = committing | 659 self.committing = committing |
660 | 660 |
661 def ExecPresubmitScript(self, script_text, presubmit_path): | 661 def ExecPresubmitScript(self, script_text, presubmit_path): |
662 """Executes a single presubmit script. | 662 """Executes a single presubmit script. |
663 | 663 |
664 Args: | 664 Args: |
665 script_text: The text of the presubmit script. | 665 script_text: The text of the presubmit script. |
666 presubmit_path: The path to the presubmit file (this will be reported via | 666 presubmit_path: The path to the presubmit file (this will be reported via |
667 input_api.PresubmitLocalPath()). | 667 input_api.PresubmitLocalPath()). |
668 | 668 |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
707 """Runs all presubmit checks that apply to the files in the change. | 707 """Runs all presubmit checks that apply to the files in the change. |
708 | 708 |
709 This finds all PRESUBMIT.py files in directories enclosing the files in the | 709 This finds all PRESUBMIT.py files in directories enclosing the files in the |
710 change (up to the repository root) and calls the relevant entrypoint function | 710 change (up to the repository root) and calls the relevant entrypoint function |
711 depending on whether the change is being committed or uploaded. | 711 depending on whether the change is being committed or uploaded. |
712 | 712 |
713 Prints errors, warnings and notifications. Prompts the user for warnings | 713 Prints errors, warnings and notifications. Prompts the user for warnings |
714 when needed. | 714 when needed. |
715 | 715 |
716 Args: | 716 Args: |
717 change_info: The ChangeInfo object for the change. | 717 change_info: The gcl.ChangeInfo object for the change. |
718 committing: True if 'gcl commit' is running, False if 'gcl upload' is. | 718 committing: True if 'gcl commit' is running, False if 'gcl upload' is. |
719 verbose: Prints debug info. | 719 verbose: Prints debug info. |
720 output_stream: A stream to write output from presubmit tests to. | 720 output_stream: A stream to write output from presubmit tests to. |
721 input_stream: A stream to read input from the user. | 721 input_stream: A stream to read input from the user. |
722 default_presubmit: A default presubmit script to execute in any case. | 722 default_presubmit: A default presubmit script to execute in any case. |
723 may_prompt: Enable (y/n) questions on warning or error. | 723 may_prompt: Enable (y/n) questions on warning or error. |
724 | 724 |
725 Return: | 725 Return: |
726 True if execution can continue, False if not. | 726 True if execution can continue, False if not. |
727 """ | 727 """ |
728 checkout_root = gcl.GetRepositoryRoot() | 728 presubmit_files = ListRelevantPresubmitFiles(change_info.GetFileNames(), |
729 presubmit_files = ListRelevantPresubmitFiles(change_info.FileList(), | 729 change_info.local_root) |
730 checkout_root) | |
731 if not presubmit_files and verbose: | 730 if not presubmit_files and verbose: |
732 output_stream.write("Warning, no presubmit.py found.\n") | 731 output_stream.write("Warning, no presubmit.py found.\n") |
733 results = [] | 732 results = [] |
734 executer = PresubmitExecuter(change_info, committing) | 733 executer = PresubmitExecuter(change_info, committing) |
735 if default_presubmit: | 734 if default_presubmit: |
736 if verbose: | 735 if verbose: |
737 output_stream.write("Running default presubmit script.\n") | 736 output_stream.write("Running default presubmit script.\n") |
738 fake_path = os.path.join(checkout_root, 'PRESUBMIT.py') | 737 fake_path = os.path.join(change_info.local_root, 'PRESUBMIT.py') |
739 results += executer.ExecPresubmitScript(default_presubmit, fake_path) | 738 results += executer.ExecPresubmitScript(default_presubmit, fake_path) |
740 for filename in presubmit_files: | 739 for filename in presubmit_files: |
741 filename = os.path.abspath(filename) | 740 filename = os.path.abspath(filename) |
742 if verbose: | 741 if verbose: |
743 output_stream.write("Running %s\n" % filename) | 742 output_stream.write("Running %s\n" % filename) |
744 # Accept CRLF presubmit script. | 743 # Accept CRLF presubmit script. |
745 presubmit_script = gcl.ReadFile(filename, 'rU') | 744 presubmit_script = gcl.ReadFile(filename, 'rU') |
746 results += executer.ExecPresubmitScript(presubmit_script, filename) | 745 results += executer.ExecPresubmitScript(presubmit_script, filename) |
747 | 746 |
748 errors = [] | 747 errors = [] |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
814 options.commit, | 813 options.commit, |
815 options.verbose, | 814 options.verbose, |
816 sys.stdout, | 815 sys.stdout, |
817 sys.stdin, | 816 sys.stdin, |
818 None, | 817 None, |
819 False) | 818 False) |
820 | 819 |
821 | 820 |
822 if __name__ == '__main__': | 821 if __name__ == '__main__': |
823 sys.exit(Main(sys.argv)) | 822 sys.exit(Main(sys.argv)) |
OLD | NEW |