2009-11-20 Adam Barth <abarth@webkit.org>
[WebKit-https.git] / WebKitTools / Scripts / modules / webkitlandingscripts.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 import os
32 import StringIO
33 import subprocess
34 import sys
35
36 from optparse import make_option
37
38 from modules.changelogs import ChangeLog
39 from modules.logging import log, tee
40 from modules.scm import CommitMessage, detect_scm_system, ScriptError, CheckoutNeedsUpdate
41 from modules.webkitport import WebKitPort
42
43 def commit_message_for_this_commit(scm):
44     changelog_paths = scm.modified_changelogs()
45     if not len(changelog_paths):
46         raise ScriptError(message="Found no modified ChangeLogs, cannot create a commit message.\n"
47                           "All changes require a ChangeLog.  See:\n"
48                           "http://webkit.org/coding/contributing.html")
49
50     changelog_messages = []
51     for changelog_path in changelog_paths:
52         log("Parsing ChangeLog: %s" % changelog_path)
53         changelog_entry = ChangeLog(changelog_path).latest_entry()
54         if not changelog_entry:
55             raise ScriptError(message="Failed to parse ChangeLog: " + os.path.abspath(changelog_path))
56         changelog_messages.append(changelog_entry)
57
58     # FIXME: We should sort and label the ChangeLog messages like commit-log-editor does.
59     return CommitMessage("".join(changelog_messages).splitlines())
60
61
62 class WebKitLandingScripts:
63     @staticmethod
64     def cleaning_options():
65         return [
66             make_option("--force-clean", action="store_true", dest="force_clean", default=False, help="Clean working directory before applying patches (removes local changes and commits)"),
67             make_option("--no-clean", action="store_false", dest="clean", default=True, help="Don't check if the working directory is clean before applying patches"),
68         ]
69
70     @staticmethod
71     def build_options():
72         return [
73             make_option("--ignore-builders", action="store_false", dest="check_builders", default=True, help="Don't check to see if the build.webkit.org builders are green before landing."),
74             make_option("--quiet", action="store_true", dest="quiet", default=False, help="Produce less console output."),
75             make_option("--non-interactive", action="store_true", dest="non_interactive", default=False, help="Never prompt the user, fail as fast as possible."),
76         ] + WebKitPort.port_options()
77
78     @staticmethod
79     def land_options():
80         return [
81             make_option("--no-close", action="store_false", dest="close_bug", default=True, help="Leave bug open after landing."),
82             make_option("--no-build", action="store_false", dest="build", default=True, help="Commit without building first, implies --no-test."),
83             make_option("--no-test", action="store_false", dest="test", default=True, help="Commit without running run-webkit-tests."),
84         ]
85
86     @staticmethod
87     def run_command_with_teed_output(args, teed_output):
88         child_process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
89
90         # Use our own custom wait loop because Popen ignores a tee'd stderr/stdout.
91         # FIXME: This could be improved not to flatten output to stdout.
92         while True:
93             output_line = child_process.stdout.readline()
94             if output_line == "" and child_process.poll() != None:
95                 return child_process.poll()
96             teed_output.write(output_line)
97
98     @staticmethod
99     def run_and_throw_if_fail(args, quiet=False):
100         # Cache the child's output locally so it can be used for error reports.
101         child_out_file = StringIO.StringIO()
102         if quiet:
103             dev_null = open(os.devnull, "w")
104         child_stdout = tee(child_out_file, dev_null if quiet else sys.stdout)
105         exit_code = WebKitLandingScripts.run_command_with_teed_output(args, child_stdout)
106         if quiet:
107             dev_null.close()
108
109         child_output = child_out_file.getvalue()
110         child_out_file.close()
111
112         if exit_code:
113             raise ScriptError(script_args=args, exit_code=exit_code, output=child_output)
114
115     @classmethod
116     def run_webkit_script(cls, script_name, quiet=False, port=WebKitPort):
117         log("Running %s" % script_name)
118         cls.run_and_throw_if_fail(port.script_path(script_name), quiet)
119
120     @classmethod
121     def build_webkit(cls, quiet=False, port=WebKitPort):
122         log("Building WebKit")
123         cls.run_and_throw_if_fail(port.build_webkit_command(), quiet)
124
125     @staticmethod
126     def ensure_builders_are_green(buildbot, options):
127         if not options.check_builders or buildbot.core_builders_are_green():
128             return
129         error("Builders at %s are red, please do not commit.  Pass --ignore-builders to bypass this check." % (buildbot.buildbot_host))
130
131     @classmethod
132     def run_webkit_tests(cls, launch_safari, fail_fast=False, quiet=False, port=WebKitPort):
133         args = port.run_webkit_tests_command()
134         if not launch_safari:
135             args.append("--no-launch-safari")
136         if quiet:
137             args.append("--quiet")
138         if fail_fast:
139             args.append("--exit-after-n-failures=1")
140         cls.run_and_throw_if_fail(args)
141
142     @staticmethod
143     def prepare_clean_working_directory(scm, options, allow_local_commits=False):
144         os.chdir(scm.checkout_root)
145         if not allow_local_commits:
146             scm.ensure_no_local_commits(options.force_clean)
147         if options.clean:
148             scm.ensure_clean_working_directory(force_clean=options.force_clean)