Changeset 51012 in webkit
- Timestamp:
- Nov 16, 2009 12:07:38 AM (14 years ago)
- Location:
- trunk/WebKitTools
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/WebKitTools/ChangeLog
r51010 r51012 1 2009-11-15 Adam Barth <abarth@webkit.org> 2 3 Reviewed by Eric Seidel. 4 5 Refactor bugzilla-tool to allow for multiple queues 6 https://bugs.webkit.org/show_bug.cgi?id=31513 7 8 Divide the commit queue class into three class to make creating 9 additional queues easier. 10 11 * Scripts/bugzilla-tool: 12 1 13 2009-11-15 Shinichiro Hamaji <hamaji@chromium.org> 2 14 -
trunk/WebKitTools/Scripts/bugzilla-tool
r51001 r51012 675 675 676 676 677 class LandPatchesFromCommitQueue(Command): 678 def __init__(self): 679 options = [ 680 make_option("--no-confirm", action="store_false", dest="confirm", default=True, help="Do not ask the user for confirmation before running the queue. Dangerous!"), 681 make_option("--status-host", action="store", type="string", dest="status_host", default=StatusBot.default_host, help="Do not ask the user for confirmation before running the queue. Dangerous!"), 682 ] 683 Command.__init__(self, 'Run the commit queue.', options=options) 677 class OutputTee: 678 def __init__(self): 684 679 self._original_stdout = None 685 680 self._original_stderr = None 686 681 self._files_for_output = [] 687 682 688 queue_log_path = 'commit_queue.log' 689 bug_logs_directory = 'commit_queue_logs' 690 691 log_date_format = "%Y-%m-%d %H:%M:%S" 692 sleep_duration_text = "5 mins" 693 seconds_to_sleep = 300 683 def add_log(self, path): 684 log_file = self._open_log_file(path) 685 self._files_for_output.append(log_file) 686 self._tee_outputs_to_files(self._files_for_output) 687 return log_file 688 689 def remove_log(self, log_file): 690 self._files_for_output.remove(log_file) 691 self._tee_outputs_to_files(self._files_for_output) 692 log_file.close() 693 694 @staticmethod 695 def _open_log_file(log_path): 696 (log_directory, log_name) = os.path.split(log_path) 697 if log_directory and not os.path.exists(log_directory): 698 os.makedirs(log_directory) 699 return open(log_path, 'a+') 694 700 695 701 def _tee_outputs_to_files(self, files): … … 704 710 sys.stderr = self._original_stderr 705 711 712 713 class WorkQueueDelegate: 714 def queue_log_path(self): 715 raise NotImplementedError, "subclasses must implement" 716 717 def work_logs_directory(self): 718 raise NotImplementedError, "subclasses must implement" 719 720 def status_host(self): 721 raise NotImplementedError, "subclasses must implement" 722 723 def begin_work_queue(self): 724 raise NotImplementedError, "subclasses must implement" 725 726 def next_work_item(self): 727 raise NotImplementedError, "subclasses must implement" 728 729 def should_proceed_with_work_item(self, work_item): 730 # returns (safe_to_proceed, waiting_message, bug_id) 731 raise NotImplementedError, "subclasses must implement" 732 733 def process_work_item(self, work_item): 734 raise NotImplementedError, "subclasses must implement" 735 736 def handle_unexpected_error(self, work_item, message): 737 raise NotImplementedError, "subclasses must implement" 738 739 740 class WorkQueue: 741 def __init__(self, delegate): 742 self._delegate = delegate 743 self._output_tee = OutputTee() 744 745 log_date_format = "%Y-%m-%d %H:%M:%S" 746 sleep_duration_text = "5 mins" 747 seconds_to_sleep = 300 748 749 def run(self): 750 self._begin_logging() 751 self.status_bot = StatusBot(host=self._delegate.status_host()) 752 753 self._delegate.begin_work_queue() 754 while (True): 755 self._ensure_work_log_closed() 756 try: 757 work_item = self._delegate.next_work_item() 758 if not work_item: 759 self._update_status_and_sleep("Empty queue.") 760 continue 761 (safe_to_proceed, waiting_message, bug_id) = self._delegate.should_proceed_with_work_item(work_item) 762 if not safe_to_proceed: 763 self._update_status_and_sleep(waiting_message, bug_ig=bug_id) 764 continue 765 self.status_bot.update_status(waiting_message, bug_id=bug_id) 766 except Exception, e: 767 # Don't try tell the status bot, in case telling it causes an exception. 768 self._sleep("Exception while preparing queue: %s." % e) 769 continue 770 771 self._open_work_log(bug_id) 772 try: 773 self._delegate.process_work_item(work_item) 774 except ScriptError, e: 775 # exit(2) is a special exit code we use to indicate that the error was already 776 # handled by and we should keep looping anyway. 777 if e.exit_code == 2: 778 continue 779 message = "Unexpected failure when landing patch! Please file a bug against bugzilla-tool.\n%s" % e.message_with_output() 780 self._delegate.handle_unexpected_error(work_item, message) 781 # Never reached. 782 self._ensure_work_log_closed() 783 784 def _begin_logging(self): 785 self._queue_log = self._output_tee.add_log(self._delegate.queue_log_path()) 786 self._work_log = None 787 788 def _open_work_log(self, bug_id): 789 work_log_path = os.path.join(self._delegate.work_logs_directory(), "%s.log" % bug_id) 790 self._work_log = self._output_tee.add_log(work_log_path) 791 792 def _ensure_work_log_closed(self): 793 # If we still have a bug log open, close it. 794 if self._work_log: 795 self._output_tee.remove_log(self._work_log) 796 self._work_log = None 797 706 798 @classmethod 707 799 def _sleep_message(cls, message): … … 720 812 time.sleep(self.seconds_to_sleep) 721 813 722 @staticmethod 723 def _open_log_file(log_path):724 (log_directory, log_name) = os.path.split(log_path)725 if log_directory and not os.path.exists(log_directory):726 os.makedirs(log_directory)727 return open(log_path, 'a+')728 729 def _add_log_to_output_tee(self, path):730 log_file = self._open_log_file(path) 731 self._files_for_output.append(log_file)732 self._tee_outputs_to_files(self._files_for_output)733 return log_file 734 735 def _remove_log_from_output_tee(self, log_file):736 self._files_for_output.remove(log_file) 737 self._tee_outputs_to_files(self._files_for_output)738 log_file.close()739 740 def execute(self, options, args, tool):741 log("CAUTION: commit-queue will discard all local changes in %s" % tool.scm().checkout_root)742 if options.confirm:814 815 class LandPatchesFromCommitQueue(Command): 816 def __init__(self): 817 options = [ 818 make_option("--no-confirm", action="store_false", dest="confirm", default=True, help="Do not ask the user for confirmation before running the queue. Dangerous!"), 819 make_option("--status-host", action="store", type="string", dest="status_host", default=StatusBot.default_host, help="Do not ask the user for confirmation before running the queue. Dangerous!"), 820 ] 821 Command.__init__(self, 'Run the commit queue.', options=options) 822 823 def queue_log_path(self): 824 return 'commit_queue.log' 825 826 def work_logs_directory(self): 827 return 'commit_queue_logs' 828 829 def status_host(self): 830 return self.options.status_host 831 832 def begin_work_queue(self): 833 log("CAUTION: commit-queue will discard all local changes in %s" % self.tool.scm().checkout_root) 834 if self.options.confirm: 743 835 response = raw_input("Are you sure? Type 'yes' to continue: ") 744 836 if (response != 'yes'): 745 837 error("User declined.") 746 747 queue_log = self._add_log_to_output_tee(self.queue_log_path) 748 log("Running WebKit Commit Queue. %s" % datetime.now().strftime(self.log_date_format)) 749 750 self.status_bot = StatusBot(host=options.status_host) 751 752 bug_log = None 753 while (True): 754 # If we still have a bug log open from the last loop, close it. 755 if bug_log: 756 self._remove_log_from_output_tee(bug_log) 757 bug_log = None 758 # Either of these calls could throw URLError which shouldn't stop the queue. 759 # We catch all exceptions just in case. 760 try: 761 # Fetch patches instead of just bug ids to that we validate reviewer/committer flags on every patch. 762 patches = tool.bugs.fetch_patches_from_commit_queue(reject_invalid_patches=True) 763 if not len(patches): 764 self._update_status_and_sleep("Empty queue.") 765 continue 766 patch_ids = map(lambda patch: patch['id'], patches) 767 first_bug_id = patches[0]['bug_id'] 768 log("%s in commit queue [%s]" % (pluralize('patch', len(patches)), ", ".join(patch_ids))) 769 770 red_builders_names = tool.buildbot.red_core_builders_names() 771 if red_builders_names: 772 red_builders_names = map(lambda name: '"%s"' % name, red_builders_names) # Add quotes around the names. 773 self._update_status_and_sleep("Builders [%s] are red. See http://build.webkit.org." % ", ".join(red_builders_names)) 774 continue 775 776 self.status_bot.update_status("Landing patches from bug %s." % first_bug_id, bug_id=first_bug_id) 777 except Exception, e: 778 # Don't try tell the status bot, in case telling it causes an exception. 779 self._sleep("Exception while checking queue and bots: %s." % e) 780 continue 781 782 # Try to land patches on the first bug in the queue before looping 783 bug_log_path = os.path.join(self.bug_logs_directory, "%s.log" % first_bug_id) 784 bug_log = self._add_log_to_output_tee(bug_log_path) 785 bugzilla_tool_path = __file__ # re-execute this script 786 bugzilla_tool_args = [bugzilla_tool_path, 'land-patches', '--force-clean', '--commit-queue', '--quiet', first_bug_id] 787 try: 788 WebKitLandingScripts.run_and_throw_if_fail(bugzilla_tool_args) 789 except ScriptError, e: 790 # Unexpected failure! Mark the patch as commit-queue- and comment in the bug. 791 # exit(2) is a special exit code we use to indicate that the error was already handled by land-patches and we should keep looping anyway. 792 if e.exit_code == 2: 793 continue 794 message = "Unexpected failure when landing patch! Please file a bug against bugzilla-tool.\n%s" % e.message_with_output() 795 # We don't have a patch id at this point, so try to grab the first patch off of the bug in question. 796 patches = tool.bugs.fetch_commit_queue_patches_from_bug(first_bug_id) 797 non_obsolete_patches = filter(lambda patch: not patch['is_obsolete'], patches) 798 if not len(non_obsolete_patches): 799 # If there are no patches left on the bug, assume land-patches already closed it before dying, and just continue. 800 log(message) 801 continue 802 first_patch_id = non_obsolete_patches[0]['id'] 803 tool.bugs.reject_patch_from_commit_queue(first_patch_id, message) 804 805 # Never reached. 806 if bug_log: 807 self._remove_log_from_output_tee(bug_log) 808 self._remove_log_from_output_tee(queue_log) 809 838 log("Running WebKit Commit Queue. %s" % datetime.now().strftime(WorkQueue.log_date_format)) 839 840 def next_work_item(self): 841 # Fetch patches instead of just bug ids to that we validate reviewer/committer flags on every patch. 842 patches = self.tool.bugs.fetch_patches_from_commit_queue(reject_invalid_patches=True) 843 if not len(patches): 844 return None 845 patch_ids = map(lambda patch: patch['id'], patches) 846 log("%s in commit queue [%s]" % (pluralize('patch', len(patches)), ", ".join(patch_ids))) 847 return patches[0]['bug_id'] 848 849 def should_proceed_with_work_item(self, bug_id): 850 red_builders_names = self.tool.buildbot.red_core_builders_names() 851 if red_builders_names: 852 red_builders_names = map(lambda name: '"%s"' % name, red_builders_names) # Add quotes around the names. 853 return (False, "Builders [%s] are red. See http://build.webkit.org." % ", ".join(red_builders_names), None) 854 return (True, "Landing patches from bug %s." % bug_id, bug_id) 855 856 def process_work_item(self, bug_id): 857 bugzilla_tool_path = __file__ # re-execute this script 858 bugzilla_tool_args = [bugzilla_tool_path, 'land-patches', '--force-clean', '--commit-queue', '--quiet', bug_id] 859 WebKitLandingScripts.run_and_throw_if_fail(bugzilla_tool_args) 860 861 def handle_unexpected_error(self, bug_id, message): 862 # We don't have a patch id at this point, so try to grab the first patch off 863 # of the bug in question. We plan to update the commit-queue to opearate 864 # off of patch ids in the near future. 865 patches = self.tool.bugs.fetch_commit_queue_patches_from_bug(bug_id) 866 non_obsolete_patches = filter(lambda patch: not patch['is_obsolete'], patches) 867 if not len(non_obsolete_patches): 868 # If there are no patches left on the bug, assume land-patches already closed it before dying, and just continue. 869 log(message) 870 return 871 bug_id = non_obsolete_patches[0]['id'] 872 self.tool.bugs.reject_patch_from_commit_queue(bug_id, message) 873 874 def execute(self, options, args, tool): 875 self.options = options 876 self.tool = tool 877 work_queue = WorkQueue(self) 878 work_queue.run() 810 879 811 880 class NonWrappingEpilogIndentedHelpFormatter(IndentedHelpFormatter):
Note: See TracChangeset
for help on using the changeset viewer.