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
48 from modules.logging import error, log, tee
49 from modules.multicommandtool import MultiCommandTool, Command
50 from modules.patchcollection import PatchCollection
51 from modules.scm import CommitMessage, detect_scm_system, ScriptError, CheckoutNeedsUpdate
52 from modules.statusbot import StatusBot
53 from modules.webkitlandingscripts import WebKitLandingScripts, commit_message_for_this_commit
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 = [tool.path()] + args
99 WebKitLandingScripts.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):
112 name = "commit-queue"
114 AbstractQueue.__init__(self)
116 def begin_work_queue(self):
117 AbstractQueue.begin_work_queue(self)
119 def next_work_item(self):
120 patches = self.tool.bugs.fetch_patches_from_commit_queue(reject_invalid_patches=True)
123 # Only bother logging if we have patches in the queue.
124 self.log_progress([patch['id'] for patch in patches])
127 def should_proceed_with_work_item(self, patch):
128 red_builders_names = self.tool.buildbot.red_core_builders_names()
129 if red_builders_names:
130 red_builders_names = map(lambda name: "\"%s\"" % name, red_builders_names) # Add quotes around the names.
131 return (False, "Builders [%s] are red. See http://build.webkit.org." % ", ".join(red_builders_names), None)
132 return (True, "Landing patch %s from bug %s." % (patch["id"], patch["bug_id"]), patch)
134 def process_work_item(self, patch):
135 self.run_bugzilla_tool(["land-attachment", "--force-clean", "--non-interactive", "--quiet", patch["id"]])
137 def handle_unexpected_error(self, patch, message):
138 self.tool.bugs.reject_patch_from_commit_queue(patch["id"], message)
141 class AbstractTryQueue(AbstractQueue):
142 def __init__(self, options=[]):
143 AbstractQueue.__init__(self, options)
145 def status_host(self):
146 return None # FIXME: A hack until we come up with a more generic status page.
148 def begin_work_queue(self):
149 AbstractQueue.begin_work_queue(self)
150 self._patches = PatchCollection(self.tool.bugs)
151 self._patches.add_patches(self.tool.bugs.fetch_patches_from_review_queue(limit=10))
153 def next_work_item(self):
154 self.log_progress(self._patches.patch_ids())
155 return self._patches.next()
157 def should_proceed_with_work_item(self, patch):
158 raise NotImplementedError, "subclasses must implement"
160 def process_work_item(self, patch):
161 raise NotImplementedError, "subclasses must implement"
163 def handle_unexpected_error(self, patch, message):
167 class StyleQueue(AbstractTryQueue):
170 AbstractTryQueue.__init__(self)
172 def should_proceed_with_work_item(self, patch):
173 return (True, "Checking style for patch %s on bug %s." % (patch["id"], patch["bug_id"]), patch)
175 def process_work_item(self, patch):
176 self.run_bugzilla_tool(["check-style", "--force-clean", patch["id"]])
179 class BuildQueue(AbstractTryQueue):
182 options = WebKitPort.port_options()
183 AbstractTryQueue.__init__(self, options)
185 def begin_work_queue(self):
186 AbstractTryQueue.begin_work_queue(self)
187 self.port = WebKitPort.port(self.options)
189 def should_proceed_with_work_item(self, patch):
191 self.run_bugzilla_tool(["build", self.port.flag(), "--force-clean", "--quiet"])
192 except ScriptError, e:
193 return (False, "Unable to perform a build.", None)
194 return (True, "Building patch %s on bug %s." % (patch["id"], patch["bug_id"]), patch)
196 def process_work_item(self, patch):
197 self.run_bugzilla_tool(["build-attachment", self.port.flag(), "--force-clean", "--quiet", "--no-update", patch["id"]])