2 # Copyright (c) 2009, Google Inc. All rights reserved.
3 # Copyright (c) 2009 Apple Inc. All rights reserved.
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are
9 # * Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
11 # * Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following disclaimer
13 # in the documentation and/or other materials provided with the
15 # * Neither the name of Google Inc. nor the names of its
16 # contributors may be used to endorse or promote products derived from
17 # this software without specific prior written permission.
19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 # FIXME: Trim down this import list once we have unit tests.
39 from datetime import datetime, timedelta
40 from optparse import make_option
42 from modules.bugzilla import Bugzilla, parse_bug_id
43 from modules.buildbot import BuildBot
44 from modules.changelogs import ChangeLog
45 from modules.comments import bug_comment_from_commit_text
46 from modules.grammar import pluralize
47 from modules.landingsequence import LandingSequence, ConditionalLandingSequence, LandingSequenceErrorHandler
48 from modules.logging import error, log, tee
49 from modules.multicommandtool import MultiCommandTool, Command
50 from modules.patchcollection import PatchCollection, PersistentPatchCollection, PersistentPatchCollectionDelegate
51 from modules.processutils import run_and_throw_if_fail
52 from modules.scm import CommitMessage, detect_scm_system, ScriptError, CheckoutNeedsUpdate
53 from modules.statusbot import StatusBot
54 from modules.webkitport import WebKitPort
55 from modules.workqueue import WorkQueue, WorkQueueDelegate
57 class AbstractQueue(Command, WorkQueueDelegate):
58 def __init__(self, options=[]):
60 make_option("--no-confirm", action="store_false", dest="confirm", default=True, help="Do not ask the user for confirmation before running the queue. Dangerous!"),
61 make_option("--status-host", action="store", type="string", dest="status_host", default=StatusBot.default_host, help="Hostname (e.g. localhost or commit.webkit.org) where status updates should be posted."),
63 Command.__init__(self, "Run the %s" % self.name, options=options)
65 def queue_log_path(self):
66 return "%s.log" % self.name
68 def work_logs_directory(self):
69 return "%s-logs" % self.name
71 def status_host(self):
72 return self.options.status_host
74 def begin_work_queue(self):
75 log("CAUTION: %s will discard all local changes in %s" % (self.name, self.tool.scm().checkout_root))
76 if self.options.confirm:
77 response = raw_input("Are you sure? Type \"yes\" to continue: ")
78 if (response != "yes"):
79 error("User declined.")
80 log("Running WebKit %s. %s" % (self.name, datetime.now().strftime(WorkQueue.log_date_format)))
82 def should_continue_work_queue(self):
85 def next_work_item(self):
86 raise NotImplementedError, "subclasses must implement"
88 def should_proceed_with_work_item(self, work_item):
89 raise NotImplementedError, "subclasses must implement"
91 def process_work_item(self, work_item):
92 raise NotImplementedError, "subclasses must implement"
94 def handle_unexpected_error(self, work_item, message):
95 raise NotImplementedError, "subclasses must implement"
97 def run_bugzilla_tool(self, args):
98 bugzilla_tool_args = [self.tool.path()] + args
99 run_and_throw_if_fail(bugzilla_tool_args)
101 def log_progress(self, patch_ids):
102 log("%s in %s [%s]" % (pluralize("patch", len(patch_ids)), self.name, ", ".join(patch_ids)))
104 def execute(self, options, args, tool):
105 self.options = options
107 work_queue = WorkQueue(self.name, self)
111 class CommitQueue(AbstractQueue, LandingSequenceErrorHandler):
112 name = "commit-queue"
113 show_in_main_help = False
115 AbstractQueue.__init__(self)
117 # AbstractQueue methods
119 def begin_work_queue(self):
120 AbstractQueue.begin_work_queue(self)
122 def next_work_item(self):
123 patches = self.tool.bugs.fetch_patches_from_commit_queue(reject_invalid_patches=True)
126 # Only bother logging if we have patches in the queue.
127 self.log_progress([patch['id'] for patch in patches])
130 def should_proceed_with_work_item(self, patch):
131 red_builders_names = self.tool.buildbot.red_core_builders_names()
132 if red_builders_names:
133 red_builders_names = map(lambda name: "\"%s\"" % name, red_builders_names) # Add quotes around the names.
134 return (False, "Builders [%s] are red. See http://build.webkit.org." % ", ".join(red_builders_names), None)
135 return (True, "Landing patch %s from bug %s." % (patch["id"], patch["bug_id"]), patch)
137 def process_work_item(self, patch):
138 self.run_bugzilla_tool(["land-attachment", "--force-clean", "--non-interactive", "--parent-command=commit-queue", "--quiet", patch["id"]])
140 def handle_unexpected_error(self, patch, message):
141 self.tool.bugs.reject_patch_from_commit_queue(patch["id"], message)
143 # LandingSequenceErrorHandler methods
146 def handle_script_error(cls, tool, patch, script_error):
147 tool.bugs.reject_patch_from_commit_queue(patch["id"], script_error.message_with_output())
150 class AbstractTryQueue(AbstractQueue, PersistentPatchCollectionDelegate, LandingSequenceErrorHandler):
151 def __init__(self, options=[]):
152 AbstractQueue.__init__(self, options)
154 # PersistentPatchCollectionDelegate methods
156 def collection_name(self):
159 def fetch_potential_patches(self):
160 return self.tool.bugs.fetch_patches_from_review_queue(limit=3)
162 def status_server(self):
163 return self.tool.status()
165 # AbstractQueue methods
167 def begin_work_queue(self):
168 AbstractQueue.begin_work_queue(self)
169 self.tool.status().set_host(self.options.status_host)
170 self._patches = PersistentPatchCollection(self)
172 def next_work_item(self):
173 return self._patches.next()
175 def should_proceed_with_work_item(self, patch):
176 raise NotImplementedError, "subclasses must implement"
178 def process_work_item(self, patch):
179 raise NotImplementedError, "subclasses must implement"
181 def handle_unexpected_error(self, patch, message):
183 self._patches.done(patch)
185 # LandingSequenceErrorHandler methods
188 def handle_script_error(cls, tool, patch, script_error):
189 log(script_error.message_with_output())
192 class StyleQueue(AbstractTryQueue):
194 show_in_main_help = False
196 AbstractTryQueue.__init__(self)
198 def should_proceed_with_work_item(self, patch):
199 return (True, "Checking style for patch %s on bug %s." % (patch["id"], patch["bug_id"]), patch)
201 def process_work_item(self, patch):
202 self.run_bugzilla_tool(["check-style", "--force-clean", "--non-interactive", "--parent-command=style-queue", patch["id"]])
203 self._patches.done(patch)
206 def handle_script_error(cls, tool, patch, script_error):
207 command = script_error.script_args
208 if type(command) is list:
210 # FIXME: We shouldn't need to use a regexp here. ScriptError should
212 if re.search("check-webkit-style", command):
213 message = "Attachment %s did not pass %s:\n\n%s" % (patch["id"], cls.name, script_error.message_with_output(output_limit=None))
214 # Local-only logging helpful for development:
215 # log("** BEGIN BUG POST **\n%s** END BUG POST **" % message)
216 tool.bugs.post_comment_to_bug(patch["bug_id"], message)
219 class BuildQueue(AbstractTryQueue):
221 show_in_main_help = False
223 options = WebKitPort.port_options()
224 AbstractTryQueue.__init__(self, options)
226 def begin_work_queue(self):
227 AbstractTryQueue.begin_work_queue(self)
228 self.port = WebKitPort.port(self.options)
230 def should_proceed_with_work_item(self, patch):
232 self.run_bugzilla_tool(["build", self.port.flag(), "--force-clean", "--quiet"])
233 except ScriptError, e:
234 return (False, "Unable to perform a build.", None)
235 return (True, "Building patch %s on bug %s." % (patch["id"], patch["bug_id"]), patch)
237 def process_work_item(self, patch):
238 self.run_bugzilla_tool(["build-attachment", self.port.flag(), "--force-clean", "--quiet", "--non-interactive", "--parent-command=build-queue", "--no-update", patch["id"]])
239 self._patches.done(patch)