fee7f6d7aad81fe37ebecb5262a6bf857a95530e
[WebKit-https.git] / WebKitTools / Scripts / modules / commands / queues.py
1 #!/usr/bin/env python
2 # Copyright (c) 2009, Google Inc. All rights reserved.
3 # Copyright (c) 2009 Apple Inc. All rights reserved.
4 #
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are
7 # met:
8
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
14 # distribution.
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.
18
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.
30
31 # FIXME: Trim down this import list once we have unit tests.
32 import os
33 import re
34 import StringIO
35 import subprocess
36 import sys
37 import time
38
39 from datetime import datetime, timedelta
40 from optparse import make_option
41
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
56
57 class AbstractQueue(Command, WorkQueueDelegate):
58     def __init__(self, options=[]):
59         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."),
62         ]
63         Command.__init__(self, "Run the %s." % self.name, options=options)
64
65     def queue_log_path(self):
66         return "%s.log" % self.name
67
68     def work_logs_directory(self):
69         return "%s-logs" % self.name
70
71     def status_host(self):
72         return self.options.status_host
73
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)))
81
82     def should_continue_work_queue(self):
83         return True
84
85     def next_work_item(self):
86         raise NotImplementedError, "subclasses must implement"
87
88     def should_proceed_with_work_item(self, work_item):
89         raise NotImplementedError, "subclasses must implement"
90
91     def process_work_item(self, work_item):
92         raise NotImplementedError, "subclasses must implement"
93
94     def handle_unexpected_error(self, work_item, message):
95         raise NotImplementedError, "subclasses must implement"
96
97     def run_bugzilla_tool(self, args):
98         bugzilla_tool_args = [tool.path()] + args
99         WebKitLandingScripts.run_and_throw_if_fail(bugzilla_tool_args)
100
101     def log_progress(self, patch_ids):
102         log("%s in %s [%s]" % (pluralize("patch", len(patch_ids)), self.name, ", ".join(patch_ids)))
103
104     def execute(self, options, args, tool):
105         self.options = options
106         self.tool = tool
107         work_queue = WorkQueue(self.name, self)
108         work_queue.run()
109
110
111 class CommitQueue(AbstractQueue):
112     name = "commit-queue"
113     def __init__(self):
114         AbstractQueue.__init__(self)
115
116     def begin_work_queue(self):
117         AbstractQueue.begin_work_queue(self)
118
119     def next_work_item(self):
120         patches = self.tool.bugs.fetch_patches_from_commit_queue(reject_invalid_patches=True)
121         if not patches:
122             return None
123         # Only bother logging if we have patches in the queue.
124         self.log_progress([patch['id'] for patch in patches])
125         return patches[0]
126
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)
133
134     def process_work_item(self, patch):
135         self.run_bugzilla_tool(["land-attachment", "--force-clean", "--non-interactive", "--quiet", patch["id"]])
136
137     def handle_unexpected_error(self, patch, message):
138         self.tool.bugs.reject_patch_from_commit_queue(patch["id"], message)
139
140
141 class AbstractTryQueue(AbstractQueue):
142     def __init__(self, options=[]):
143         AbstractQueue.__init__(self, options)
144
145     def status_host(self):
146         return None # FIXME: A hack until we come up with a more generic status page.
147
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))
152
153     def next_work_item(self):
154         self.log_progress(self._patches.patch_ids())
155         return self._patches.next()
156
157     def should_proceed_with_work_item(self, patch):
158         raise NotImplementedError, "subclasses must implement"
159
160     def process_work_item(self, patch):
161         raise NotImplementedError, "subclasses must implement"
162
163     def handle_unexpected_error(self, patch, message):
164         log(message)
165
166
167 class StyleQueue(AbstractTryQueue):
168     name = "style-queue"
169     def __init__(self):
170         AbstractTryQueue.__init__(self)
171
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)
174
175     def process_work_item(self, patch):
176         self.run_bugzilla_tool(["check-style", "--force-clean", patch["id"]])
177
178
179 class BuildQueue(AbstractTryQueue):
180     name = "build-queue"
181     def __init__(self):
182         options = WebKitPort.port_options()
183         AbstractTryQueue.__init__(self, options)
184
185     def begin_work_queue(self):
186         AbstractTryQueue.begin_work_queue(self)
187         self.port = WebKitPort.port(self.options)
188
189     def should_proceed_with_work_item(self, patch):
190         try:
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)
195
196     def process_work_item(self, patch):
197         self.run_bugzilla_tool(["build-attachment", self.port.flag(), "--force-clean", "--quiet", "--no-update", patch["id"]])