OLD | NEW |
1 #!/usr/bin/python2.5 | 1 #!/usr/bin/python2.5 |
2 # Copyright (c) 2009 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2009 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Snapshot Build Bisect Tool | 6 """Snapshot Build Bisect Tool |
7 | 7 |
8 This script bisects the Mac snapshot archive using binary search. It starts at | 8 This script bisects a snapshot archive using binary search. It starts at |
9 a bad revision (it will try to guess HEAD) and asks for a last known-good | 9 a bad revision (it will try to guess HEAD) and asks for a last known-good |
10 revision. It will then binary search across this revision range by downloading, | 10 revision. It will then binary search across this revision range by downloading, |
11 unzipping, and opening Chromium for you. After testing the specific revision, | 11 unzipping, and opening Chromium for you. After testing the specific revision, |
12 it will ask you whether it is good or bad before continuing the search. | 12 it will ask you whether it is good or bad before continuing the search. |
13 | |
14 Currently this only works on Mac, but with some effort it could be ported to | |
15 other platforms. | |
16 """ | 13 """ |
17 | 14 |
18 # Base URL to download snapshots from. | 15 # Base URL to download snapshots from. |
19 BUILD_BASE_URL = \ | 16 BUILD_BASE_URL = "http://build.chromium.org/buildbot/snapshots/" |
20 "http://build.chromium.org/buildbot/snapshots/chromium-rel-mac" | |
21 | 17 |
22 # Location of the latest build revision number | 18 # The type (platform) of the build archive. This is what's passed in to the |
23 BUILD_LATEST_URL = "%s/LATEST" % BUILD_BASE_URL | 19 # '-a/--archive' option. |
| 20 BUILD_ARCHIVE_TYPE = '' |
| 21 |
| 22 # The selected archive to bisect. |
| 23 BUILD_ARCHIVE_DIR = '' |
24 | 24 |
25 # The location of the builds. | 25 # The location of the builds. |
26 BUILD_ARCHIVE_URL = "/%d/" | 26 BUILD_ARCHIVE_URL = "/%d/" |
27 | 27 |
28 # Name of the build archive. | 28 # Name of the build archive. |
29 BUILD_ZIP_NAME = "chrome-mac.zip" | 29 BUILD_ZIP_NAME = '' |
30 | 30 |
31 # Directory name inside the archive. | 31 # Directory name inside the archive. |
32 BUILD_DIR_NAME = "chrome-mac" | 32 BUILD_DIR_NAME = '' |
33 | 33 |
34 # Name of the executable. | 34 # Name of the executable. |
35 BUILD_EXE_NAME = "Chromium.app" | 35 BUILD_EXE_NAME = '' |
36 | 36 |
37 # URL to the ViewVC commit page. | 37 # URL to the ViewVC commit page. |
38 BUILD_VIEWVC_URL = "http://src.chromium.org/viewvc/chrome?view=rev&revision=%d" | 38 BUILD_VIEWVC_URL = "http://src.chromium.org/viewvc/chrome?view=rev&revision=%d" |
39 | 39 |
40 ############################################################################### | 40 ############################################################################### |
41 | 41 |
42 import math | 42 import math |
| 43 import optparse |
43 import os | 44 import os |
44 import re | 45 import re |
45 import shutil | 46 import shutil |
46 import sys | 47 import sys |
| 48 import tempfile |
47 import urllib | 49 import urllib |
48 | 50 |
| 51 def SetArchiveVars(archive): |
| 52 """Set a bunch of global variables appropriate for the specified archive.""" |
| 53 global BUILD_ARCHIVE_TYPE |
| 54 global BUILD_ARCHIVE_DIR |
| 55 global BUILD_ZIP_NAME |
| 56 global BUILD_DIR_NAME |
| 57 global BUILD_EXE_NAME |
| 58 global BUILD_BASE_URL |
| 59 |
| 60 BUILD_ARCHIVE_TYPE = archive |
| 61 BUILD_ARCHIVE_DIR = 'chromium-rel-' + BUILD_ARCHIVE_TYPE |
| 62 |
| 63 if BUILD_ARCHIVE_TYPE in ('linux', 'linux-64'): |
| 64 BUILD_ZIP_NAME = 'chrome-linux.zip' |
| 65 BUILD_DIR_NAME = 'chrome-linux' |
| 66 BUILD_EXE_NAME = "chrome" |
| 67 elif BUILD_ARCHIVE_TYPE in ('mac'): |
| 68 BUILD_ZIP_NAME = 'chrome-mac.zip' |
| 69 BUILD_DIR_NAME = 'chrome-mac' |
| 70 BUILD_EXE_NAME = "Chromium.app" |
| 71 elif BUILD_ARCHIVE_TYPE in ('xp'): |
| 72 BUILD_ZIP_NAME = 'chrome-win32.zip' |
| 73 BUILD_DIR_NAME = 'chrome-win32' |
| 74 BUILD_EXE_NAME = "chrome.exe" |
| 75 |
| 76 BUILD_BASE_URL += BUILD_ARCHIVE_DIR |
| 77 |
49 def ParseDirectoryIndex(url): | 78 def ParseDirectoryIndex(url): |
50 """Parses the HTML directory listing into a list of revision numbers.""" | 79 """Parses the HTML directory listing into a list of revision numbers.""" |
51 handle = urllib.urlopen(url) | 80 handle = urllib.urlopen(url) |
52 dirindex = handle.read() | 81 dirindex = handle.read() |
53 handle.close() | 82 handle.close() |
54 return re.findall(r'<a href="([0-9]*)/">\1/</a>', dirindex) | 83 return re.findall(r'<a href="([0-9]*)/">\1/</a>', dirindex) |
55 | 84 |
56 def GetRevList(good, bad): | 85 def GetRevList(good, bad): |
57 """Gets the list of revision numbers between |good| and |bad|.""" | 86 """Gets the list of revision numbers between |good| and |bad|.""" |
58 # Download the main revlist. | 87 # Download the main revlist. |
59 revlist = ParseDirectoryIndex(BUILD_BASE_URL) | 88 revlist = ParseDirectoryIndex(BUILD_BASE_URL) |
60 revlist = map(int, revlist) | 89 revlist = map(int, revlist) |
61 revlist = filter(lambda r: range(good, bad).__contains__(int(r)), revlist) | 90 revlist = filter(lambda r: range(good, bad).__contains__(int(r)), revlist) |
62 revlist.sort() | 91 revlist.sort() |
63 return revlist | 92 return revlist |
64 | 93 |
65 def TryRevision(rev): | 94 def TryRevision(rev): |
66 """Downloads revision |rev|, unzips it, and opens it for the user to test.""" | 95 """Downloads revision |rev|, unzips it, and opens it for the user to test.""" |
67 # Clear anything that's currently there. | 96 # Do this in a temp dir so we don't collide with user files. |
68 try: | 97 cwd = os.getcwd() |
69 os.remove(BUILD_ZIP_NAME) | 98 tempdir = tempfile.mkdtemp(prefix='bisect_tmp') |
70 shutil.rmtree(BUILD_DIR_NAME, True) | 99 os.chdir(tempdir) |
71 except Exception, e: | |
72 pass | |
73 | 100 |
74 # Download the file. | 101 # Download the file. |
75 download_url = BUILD_BASE_URL + (BUILD_ARCHIVE_URL % rev) + BUILD_ZIP_NAME | 102 download_url = BUILD_BASE_URL + (BUILD_ARCHIVE_URL % rev) + BUILD_ZIP_NAME |
76 try: | 103 try: |
| 104 print 'Fetching ' + download_url |
77 urllib.urlretrieve(download_url, BUILD_ZIP_NAME) | 105 urllib.urlretrieve(download_url, BUILD_ZIP_NAME) |
78 except Exception, e: | 106 except Exception, e: |
79 print("Could not retrieve the download. Sorry.") | 107 print("Could not retrieve the download. Sorry.") |
80 print("Tried to get: %s" % download_url) | |
81 sys.exit(-1) | 108 sys.exit(-1) |
82 | 109 |
83 # Unzip the file. | 110 # Unzip the file. |
| 111 print 'Unzipping ...' |
84 os.system("unzip -q %s" % BUILD_ZIP_NAME) | 112 os.system("unzip -q %s" % BUILD_ZIP_NAME) |
85 | 113 |
86 # Tell Finder to open the app. | 114 # Tell the system to open the app. |
87 os.system("open %s/%s" % (BUILD_DIR_NAME, BUILD_EXE_NAME)) | 115 print 'Running %s/%s/%s' % (os.getcwd(), BUILD_DIR_NAME, BUILD_EXE_NAME) |
| 116 if BUILD_ARCHIVE_TYPE in ('linux', 'linux-64'): |
| 117 os.system("%s/%s" % (BUILD_DIR_NAME, BUILD_EXE_NAME)) |
| 118 elif BUILD_ARCHIVE_TYPE in ('mac'): |
| 119 os.system("open %s/%s" % (BUILD_DIR_NAME, BUILD_EXE_NAME)) |
| 120 elif BUILD_ARCHIVE_TYPE in ('xp'): |
| 121 # TODO(mmoss) Does Windows need 'start' or something? |
| 122 os.system("%s/%s" % (BUILD_DIR_NAME, BUILD_EXE_NAME)) |
| 123 |
| 124 os.chdir(cwd) |
| 125 print 'Cleaning temp dir ...' |
| 126 try: |
| 127 shutil.rmtree(tempdir, True) |
| 128 except Exception, e: |
| 129 pass |
88 | 130 |
89 def AskIsGoodBuild(rev): | 131 def AskIsGoodBuild(rev): |
90 """Annoyingly ask the user whether build |rev| is good or bad.""" | 132 """Annoyingly ask the user whether build |rev| is good or bad.""" |
91 while True: | 133 while True: |
92 check = raw_input("Build %d [g/b]: " % int(rev))[0] | 134 check = raw_input("\nBuild %d is [(g)ood/(b)ad]: " % int(rev))[0] |
93 if (check == "g" or check == "b"): | 135 if (check == "g" or check == "b"): |
94 return (check == "g") | 136 return (check == "g") |
95 else: | 137 else: |
96 print("Just answer the question...") | 138 print("Just answer the question...") |
97 | 139 |
98 def main(): | 140 def main(): |
99 print("chrome-bisect: Perform binary search on the snapshot builds") | 141 usage = ('%prog [options]\n' |
| 142 'Perform binary search on the snapshot builds.') |
| 143 parser = optparse.OptionParser(usage=usage) |
| 144 parser.add_option('-a', '--archive', |
| 145 choices = ['mac', 'xp', 'linux', 'linux-64'], |
| 146 help = 'The buildbot archive to bisect.') |
| 147 parser.add_option('-b', '--bad', type = 'int', |
| 148 help = 'The bad revision to bisect to.') |
| 149 parser.add_option('-g', '--good', type = 'int', |
| 150 help = 'The last known good revision to bisect from.') |
| 151 (opts, args) = parser.parse_args() |
| 152 |
| 153 if opts.archive is None: |
| 154 parser.print_help() |
| 155 return 1 |
| 156 |
| 157 if opts.bad and opts.good and (opts.good > opts.bad): |
| 158 print ('The good revision (%d) must precede the bad revision (%d).\n' % |
| 159 (opts.good, opts.bad)) |
| 160 parser.print_help() |
| 161 return 1 |
| 162 |
| 163 SetArchiveVars(opts.archive) |
100 | 164 |
101 # Pick a starting point, try to get HEAD for this. | 165 # Pick a starting point, try to get HEAD for this. |
102 bad_rev = 0 | 166 if opts.bad: |
103 try: | 167 bad_rev = opts.bad |
104 nh = urllib.urlopen(BUILD_LATEST_URL) | 168 else: |
105 latest = int(nh.read()) | 169 bad_rev = 0 |
106 nh.close() | 170 try: |
107 bad_rev = raw_input("Bad revision [HEAD:%d]: " % latest) | 171 # Location of the latest build revision number |
108 if (bad_rev == ""): | 172 BUILD_LATEST_URL = "%s/LATEST" % (BUILD_BASE_URL) |
109 bad_rev = latest | 173 nh = urllib.urlopen(BUILD_LATEST_URL) |
110 bad_rev = int(bad_rev) | 174 latest = int(nh.read()) |
111 except Exception, e: | 175 nh.close() |
112 print("Could not determine latest revision. This could be bad...") | 176 bad_rev = raw_input("Bad revision [HEAD:%d]: " % latest) |
113 bad_rev = int(raw_input("Bad revision: ")) | 177 if (bad_rev == ""): |
| 178 bad_rev = latest |
| 179 bad_rev = int(bad_rev) |
| 180 except Exception, e: |
| 181 print("Could not determine latest revision. This could be bad...") |
| 182 bad_rev = int(raw_input("Bad revision: ")) |
114 | 183 |
115 # Find out when we were good. | 184 # Find out when we were good. |
116 good_rev = 0 | 185 if opts.good: |
117 try: | 186 good_rev = opts.good |
118 good_rev = int(raw_input("Last known good [0]: ")) | 187 else: |
119 except Exception, e: | 188 good_rev = 0 |
120 pass | 189 try: |
| 190 good_rev = int(raw_input("Last known good [0]: ")) |
| 191 except Exception, e: |
| 192 pass |
121 | 193 |
122 # Get a list of revisions to bisect across. | 194 # Get a list of revisions to bisect across. |
123 revlist = GetRevList(good_rev, bad_rev) | 195 revlist = GetRevList(good_rev, bad_rev) |
124 | 196 |
125 # If we don't have a |good_rev|, set it to be the first revision possible. | 197 # If we don't have a |good_rev|, set it to be the first revision possible. |
126 if good_rev == 0: | 198 if good_rev == 0: |
127 good_rev = revlist[0] | 199 good_rev = revlist[0] |
128 | 200 |
129 # These are indexes of |revlist|. | 201 # These are indexes of |revlist|. |
130 good = 0 | 202 good = 0 |
(...skipping 19 matching lines...) Expand all Loading... |
150 good = test + 1 | 222 good = test + 1 |
151 else: | 223 else: |
152 bad = test | 224 bad = test |
153 | 225 |
154 # We're done. Let the user know the results in an official manner. | 226 # We're done. Let the user know the results in an official manner. |
155 print("You are probably looking for build %d." % revlist[bad]) | 227 print("You are probably looking for build %d." % revlist[bad]) |
156 print("This is the ViewVC URL for the potential bustage:") | 228 print("This is the ViewVC URL for the potential bustage:") |
157 print(BUILD_VIEWVC_URL % revlist[bad]) | 229 print(BUILD_VIEWVC_URL % revlist[bad]) |
158 | 230 |
159 if __name__ == '__main__': | 231 if __name__ == '__main__': |
160 main() | 232 sys.exit(main()) |
OLD | NEW |