OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # | 2 # |
3 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 3 # Copyright (c) 2013 The Chromium Authors. All rights reserved. |
4 # Use of this source code is governed by a BSD-style license that can be | 4 # Use of this source code is governed by a BSD-style license that can be |
5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
6 | 6 |
7 """Updates the Chrome reference builds. | 7 """Updates the Chrome reference builds. |
8 | 8 |
9 Use -r option to update a Chromium reference build, or -b option for Chrome | 9 To update Chromium (unofficial) reference build, use the -r option and give a |
10 official builds. | 10 Chromium SVN revision number, like 228977. To update a Chrome (official) build, |
| 11 use the -v option and give a Chrome version number like 30.0.1595.0. |
| 12 |
| 13 If you're getting a Chrome build, you can give the flag --gs to fetch from |
| 14 Google Storage. Otherwise, it will be fetched from go/chrome_official_builds. |
| 15 |
| 16 Before running this script, you should first verify that you are authenticated |
| 17 for SVN. You can do this by running: |
| 18 $ svn ls svn://svn.chromium.org/chrome/trunk/deps/reference_builds |
| 19 You may need to get your SVN password from https://chromium-access.appspot.com/. |
11 | 20 |
12 Usage: | 21 Usage: |
13 $ cd /tmp | 22 $ cd /tmp |
14 $ /path/to/update_reference_build.py -r <revision> | 23 $ /path/to/update_reference_build.py --gs -v <version> |
15 $ cd reference_builds/reference_builds | 24 $ cd reference_builds/reference_builds |
16 $ gcl change | 25 $ gcl change |
17 $ gcl upload <change> | 26 $ gcl upload <change> |
18 $ gcl commit <change> | 27 $ gcl commit <change> |
19 """ | 28 """ |
20 | 29 |
21 import logging | 30 import logging |
22 import optparse | 31 import optparse |
23 import os | 32 import os |
24 import shutil | 33 import shutil |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
91 | 100 |
92 _PLATFORM_DEST_MAP = { | 101 _PLATFORM_DEST_MAP = { |
93 'Linux': 'chrome_linux', | 102 'Linux': 'chrome_linux', |
94 'Linux_x64': 'chrome_linux64', | 103 'Linux_x64': 'chrome_linux64', |
95 'Win': 'chrome_win', | 104 'Win': 'chrome_win', |
96 'Mac': 'chrome_mac', | 105 'Mac': 'chrome_mac', |
97 } | 106 } |
98 | 107 |
99 def __init__(self, options): | 108 def __init__(self, options): |
100 self._platforms = options.platforms.split(',') | 109 self._platforms = options.platforms.split(',') |
101 self._revision = options.build_number or int(options.revision) | 110 self._version_or_revision = options.version or int(options.revision) |
102 self._use_build_number = bool(options.build_number) | 111 self._use_official_version = bool(options.version) |
103 self._use_gs = options.use_gs | 112 self._use_gs = options.use_gs |
104 | 113 |
105 @staticmethod | 114 @staticmethod |
106 def _GetCmdStatusAndOutput(args, cwd=None, shell=False): | 115 def _GetCmdStatusAndOutput(args, cwd=None, shell=False): |
107 """Executes a subprocess and returns its exit code and output. | 116 """Executes a subprocess and returns its exit code and output. |
108 | 117 |
109 Args: | 118 Args: |
110 args: A string or a sequence of program arguments. | 119 args: A string or a sequence of program arguments. |
111 cwd: If not None, the subprocess's current directory will be changed to | 120 cwd: If not None, the subprocess's current directory will be changed to |
112 |cwd| before it's executed. | 121 |cwd| before it's executed. |
113 shell: Whether to execute args as a shell command. | 122 shell: Whether to execute args as a shell command. |
114 | 123 |
115 Returns: | 124 Returns: |
116 The tuple (exit code, output). | 125 The tuple (exit code, output). |
117 """ | 126 """ |
118 logging.info(str(args) + ' ' + (cwd or '')) | 127 logging.info(str(args) + ' ' + (cwd or '')) |
119 p = subprocess.Popen(args=args, cwd=cwd, stdout=subprocess.PIPE, | 128 p = subprocess.Popen(args=args, cwd=cwd, stdout=subprocess.PIPE, |
120 stderr=subprocess.PIPE, shell=shell) | 129 stderr=subprocess.PIPE, shell=shell) |
121 stdout, stderr = p.communicate() | 130 stdout, stderr = p.communicate() |
122 exit_code = p.returncode | 131 exit_code = p.returncode |
123 if stderr: | 132 if stderr: |
124 logging.critical(stderr) | 133 logging.critical(stderr) |
125 logging.info(stdout) | 134 logging.info(stdout) |
126 return (exit_code, stdout) | 135 return (exit_code, stdout) |
127 | 136 |
128 def _GetBuildUrl(self, platform, revision, filename): | 137 def _GetBuildUrl(self, platform, version_or_revision, filename): |
129 if self._use_build_number: | 138 """Returns the URL for fetching one file. |
| 139 |
| 140 Args: |
| 141 platform: Platform name, must be a key in |self._BUILD_PLATFORM_MAP|. |
| 142 version_or_revision: Either an SVN revision, e.g. 234567, or a Chrome |
| 143 version number, e.g. 30.0.1600.1. |
| 144 filename: Name of the file to fetch. |
| 145 |
| 146 Returns: |
| 147 The URL for fetching a file. This may be a GS or HTTP URL. |
| 148 """ |
| 149 if self._use_official_version: |
130 # Chrome Google storage bucket. | 150 # Chrome Google storage bucket. |
| 151 version = version_or_revision |
131 if self._use_gs: | 152 if self._use_gs: |
132 release = revision[:revision.find('.')] | 153 release = version[:version.find('.')] |
133 return (CHROME_GS_URL_FMT % ( | 154 return (CHROME_GS_URL_FMT % ( |
134 release, | 155 release, |
135 revision, | 156 version, |
136 self._BUILD_PLATFORM_MAP[platform], | 157 self._BUILD_PLATFORM_MAP[platform], |
137 filename)) | 158 filename)) |
138 # Chrome internal archive. | 159 # Chrome internal archive. |
139 return (CHROME_INTERNAL_URL_FMT % ( | 160 return (CHROME_INTERNAL_URL_FMT % ( |
140 revision, | 161 version, |
141 self._BUILD_PLATFORM_MAP[platform], | 162 self._BUILD_PLATFORM_MAP[platform], |
142 filename)) | 163 filename)) |
143 # Chromium archive. | 164 # Chromium archive. |
| 165 revision = version_or_revision |
144 return CHROMIUM_URL_FMT % (urllib.quote_plus(platform), revision, filename) | 166 return CHROMIUM_URL_FMT % (urllib.quote_plus(platform), revision, filename) |
145 | 167 |
146 def _FindBuildRevision(self, platform, revision, filename): | 168 def _FindBuildVersionOrRevision( |
147 # TODO(shadi): Iterate over build numbers to find a valid one. | 169 self, platform, version_or_revision, filename): |
148 if self._use_build_number: | 170 """Searches for a version or revision where a filename can be found. |
149 return (revision | |
150 if self._DoesBuildExist(platform, revision, filename) else None) | |
151 | 171 |
| 172 Args: |
| 173 platform: Platform name. |
| 174 version_or_revision: Either Chrome version or Chromium revision. |
| 175 filename: Filename to look for. |
| 176 |
| 177 Returns: |
| 178 A version or revision where the file could be found, or None. |
| 179 """ |
| 180 # TODO(shadi): Iterate over official versions to find a valid one. |
| 181 if self._use_official_version: |
| 182 version = version_or_revision |
| 183 return (version |
| 184 if self._DoesBuildExist(platform, version, filename) else None) |
| 185 |
| 186 revision = version_or_revision |
152 MAX_REVISIONS_PER_BUILD = 100 | 187 MAX_REVISIONS_PER_BUILD = 100 |
153 for revision_guess in xrange(revision, revision + MAX_REVISIONS_PER_BUILD): | 188 for revision_guess in xrange(revision, revision + MAX_REVISIONS_PER_BUILD): |
154 if self._DoesBuildExist(platform, revision_guess, filename): | 189 if self._DoesBuildExist(platform, revision_guess, filename): |
155 return revision_guess | 190 return revision_guess |
156 else: | 191 else: |
157 time.sleep(.1) | 192 time.sleep(.1) |
158 return None | 193 return None |
159 | 194 |
160 def _DoesBuildExist(self, platform, build_number, filename): | 195 def _DoesBuildExist(self, platform, version, filename): |
161 url = self._GetBuildUrl(platform, build_number, filename) | 196 """Checks whether a file can be found for the given Chrome version. |
| 197 |
| 198 Args: |
| 199 platform: Platform name. |
| 200 version: Chrome version number, e.g. 30.0.1600.1. |
| 201 filename: Filename to look for. |
| 202 |
| 203 Returns: |
| 204 True if the file could be found, False otherwise. |
| 205 """ |
| 206 url = self._GetBuildUrl(platform, version, filename) |
162 if self._use_gs: | 207 if self._use_gs: |
163 return self._DoesGSFileExist(url) | 208 return self._DoesGSFileExist(url) |
164 | 209 |
165 r = urllib2.Request(url) | 210 request = urllib2.Request(url) |
166 r.get_method = lambda: 'HEAD' | 211 request.get_method = lambda: 'HEAD' |
167 try: | 212 try: |
168 urllib2.urlopen(r) | 213 urllib2.urlopen(request) |
169 return True | 214 return True |
170 except urllib2.HTTPError, err: | 215 except urllib2.HTTPError, err: |
171 if err.code == 404: | 216 if err.code == 404: |
172 return False | 217 return False |
173 | 218 |
174 def _DoesGSFileExist(self, gs_file_name): | 219 def _DoesGSFileExist(self, gs_file_name): |
| 220 """Returns True if the GS file can be found, False otherwise.""" |
175 exit_code = BuildUpdater._GetCmdStatusAndOutput( | 221 exit_code = BuildUpdater._GetCmdStatusAndOutput( |
176 ['gsutil', 'ls', gs_file_name])[0] | 222 ['gsutil', 'ls', gs_file_name])[0] |
177 return not exit_code | 223 return not exit_code |
178 | 224 |
179 def _GetPlatformFiles(self, platform): | 225 def _GetPlatformFiles(self, platform): |
180 if self._use_build_number: | 226 """Returns a list of filenames to fetch for the given platform.""" |
| 227 if self._use_official_version: |
181 return BuildUpdater._CHROME_PLATFORM_FILES_MAP[platform] | 228 return BuildUpdater._CHROME_PLATFORM_FILES_MAP[platform] |
182 return BuildUpdater._PLATFORM_FILES_MAP[platform] | 229 return BuildUpdater._PLATFORM_FILES_MAP[platform] |
183 | 230 |
184 def _DownloadBuilds(self): | 231 def _DownloadBuilds(self): |
185 for platform in self._platforms: | 232 for platform in self._platforms: |
186 for f in self._GetPlatformFiles(platform): | 233 for filename in self._GetPlatformFiles(platform): |
187 output = os.path.join('dl', platform, | 234 output = os.path.join('dl', platform, |
188 '%s_%s_%s' % (platform, self._revision, f)) | 235 '%s_%s_%s' % (platform, |
| 236 self._version_or_revision, |
| 237 filename)) |
189 if os.path.exists(output): | 238 if os.path.exists(output): |
190 logging.info('%s alread exists, skipping download', output) | 239 logging.info('%s alread exists, skipping download', output) |
191 continue | 240 continue |
192 build_revision = self._FindBuildRevision(platform, self._revision, f) | 241 version_or_revision = self._FindBuildVersionOrRevision( |
193 if not build_revision: | 242 platform, self._version_or_revision, filename) |
| 243 if not version_or_revision: |
194 logging.critical('Failed to find %s build for r%s\n', platform, | 244 logging.critical('Failed to find %s build for r%s\n', platform, |
195 self._revision) | 245 self._version_or_revision) |
196 sys.exit(1) | 246 sys.exit(1) |
197 dirname = os.path.dirname(output) | 247 dirname = os.path.dirname(output) |
198 if dirname and not os.path.exists(dirname): | 248 if dirname and not os.path.exists(dirname): |
199 os.makedirs(dirname) | 249 os.makedirs(dirname) |
200 url = self._GetBuildUrl(platform, build_revision, f) | 250 url = self._GetBuildUrl(platform, version_or_revision, filename) |
201 self._DownloadFile(url, output) | 251 self._DownloadFile(url, output) |
202 | 252 |
203 def _DownloadFile(self, url, output): | 253 def _DownloadFile(self, url, output): |
204 logging.info('Downloading %s, saving to %s', url, output) | 254 logging.info('Downloading %s, saving to %s', url, output) |
205 if self._use_build_number and self._use_gs: | 255 if self._use_official_version and self._use_gs: |
206 BuildUpdater._GetCmdStatusAndOutput(['gsutil', 'cp', url, output]) | 256 BuildUpdater._GetCmdStatusAndOutput(['gsutil', 'cp', url, output]) |
207 else: | 257 else: |
208 r = urllib2.urlopen(url) | 258 response = urllib2.urlopen(url) |
209 with file(output, 'wb') as f: | 259 with file(output, 'wb') as f: |
210 f.write(r.read()) | 260 f.write(response.read()) |
211 | 261 |
212 def _FetchSvnRepos(self): | 262 def _FetchSvnRepos(self): |
213 if not os.path.exists('reference_builds'): | 263 if not os.path.exists('reference_builds'): |
214 os.makedirs('reference_builds') | 264 os.makedirs('reference_builds') |
215 BuildUpdater._GetCmdStatusAndOutput( | 265 BuildUpdater._GetCmdStatusAndOutput( |
216 ['gclient', 'config', | 266 ['gclient', 'config', |
217 'svn://svn.chromium.org/chrome/trunk/deps/reference_builds'], | 267 'svn://svn.chromium.org/chrome/trunk/deps/reference_builds'], |
218 'reference_builds') | 268 'reference_builds') |
219 BuildUpdater._GetCmdStatusAndOutput( | 269 BuildUpdater._GetCmdStatusAndOutput( |
220 ['gclient', 'sync'], 'reference_builds') | 270 ['gclient', 'sync'], 'reference_builds') |
221 | 271 |
222 def _UnzipFile(self, dl_file, dest_dir): | 272 def _UnzipFile(self, dl_file, dest_dir): |
| 273 """Unzips a file if it is a zip file. |
| 274 |
| 275 Args: |
| 276 dl_file: The downloaded file to unzip. |
| 277 dest_dir: The destination directory to unzip to. |
| 278 |
| 279 Returns: |
| 280 True if the file was unzipped. False if it wasn't a zip file. |
| 281 """ |
223 if not zipfile.is_zipfile(dl_file): | 282 if not zipfile.is_zipfile(dl_file): |
224 return False | 283 return False |
225 logging.info('Opening %s', dl_file) | 284 logging.info('Opening %s', dl_file) |
226 with zipfile.ZipFile(dl_file, 'r') as z: | 285 with zipfile.ZipFile(dl_file, 'r') as z: |
227 for content in z.namelist(): | 286 for content in z.namelist(): |
228 dest = os.path.join(dest_dir, content[content.find('/')+1:]) | 287 dest = os.path.join(dest_dir, content[content.find('/')+1:]) |
229 # Create dest parent dir if it does not exist. | 288 # Create dest parent dir if it does not exist. |
230 if not os.path.isdir(os.path.dirname(dest)): | 289 if not os.path.isdir(os.path.dirname(dest)): |
231 os.makedirs(os.path.dirname(dest)) | 290 os.makedirs(os.path.dirname(dest)) |
232 # If dest is just a dir listing, do nothing. | 291 # If dest is just a dir listing, do nothing. |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
290 self._DownloadBuilds() | 349 self._DownloadBuilds() |
291 self._FetchSvnRepos() | 350 self._FetchSvnRepos() |
292 self._ExtractBuilds() | 351 self._ExtractBuilds() |
293 self._SvnAddAndRemove() | 352 self._SvnAddAndRemove() |
294 | 353 |
295 | 354 |
296 def ParseOptions(argv): | 355 def ParseOptions(argv): |
297 parser = optparse.OptionParser() | 356 parser = optparse.OptionParser() |
298 usage = 'usage: %prog <options>' | 357 usage = 'usage: %prog <options>' |
299 parser.set_usage(usage) | 358 parser.set_usage(usage) |
300 parser.add_option('-b', dest='build_number', | 359 parser.add_option('-v', dest='version', |
301 help='Chrome official build number to pick up.') | 360 help='Chrome official version to pick up ' |
| 361 '(e.g. 30.0.1600.1).') |
302 parser.add_option('--gs', dest='use_gs', action='store_true', default=False, | 362 parser.add_option('--gs', dest='use_gs', action='store_true', default=False, |
303 help='Use Google storage for official builds. Used with -b ' | 363 help='Use Google storage for official builds. Used with -b ' |
304 'option. Default is false (i.e. use internal storage.') | 364 'option. Default is false (i.e. use internal storage.') |
305 parser.add_option('-p', dest='platforms', | 365 parser.add_option('-p', dest='platforms', |
306 default='Win,Mac,Linux,Linux_x64', | 366 default='Win,Mac,Linux,Linux_x64', |
307 help='Comma separated list of platforms to download ' | 367 help='Comma separated list of platforms to download ' |
308 '(as defined by the chromium builders).') | 368 '(as defined by the chromium builders).') |
309 parser.add_option('-r', dest='revision', | 369 parser.add_option('-r', dest='revision', |
310 help='Revision to pick up.') | 370 help='Chromium revision to pick up (e.g. 234567).') |
311 | 371 |
312 (options, _) = parser.parse_args(argv) | 372 (options, _) = parser.parse_args(argv) |
313 if not options.revision and not options.build_number: | 373 if not options.revision and not options.version: |
314 logging.critical('Must specify either -r or -b.\n') | 374 logging.critical('Must specify either -r or -v.\n') |
315 sys.exit(1) | 375 sys.exit(1) |
316 if options.revision and options.build_number: | 376 if options.revision and options.version: |
317 logging.critical('Must specify either -r or -b but not both.\n') | 377 logging.critical('Must specify either -r or -v but not both.\n') |
318 sys.exit(1) | 378 sys.exit(1) |
319 if options.use_gs and not options.build_number: | 379 if options.use_gs and not options.version: |
320 logging.critical('Can only use --gs with -b option.\n') | 380 logging.critical('Can only use --gs with -v option.\n') |
321 sys.exit(1) | 381 sys.exit(1) |
322 | 382 |
323 return options | 383 return options |
324 | 384 |
325 | 385 |
326 def main(argv): | 386 def main(argv): |
327 logging.getLogger().setLevel(logging.DEBUG) | 387 logging.getLogger().setLevel(logging.DEBUG) |
328 options = ParseOptions(argv) | 388 options = ParseOptions(argv) |
329 b = BuildUpdater(options) | 389 b = BuildUpdater(options) |
330 b.DownloadAndUpdateBuilds() | 390 b.DownloadAndUpdateBuilds() |
331 logging.info('Successfully updated reference builds. Move to ' | 391 logging.info('Successfully updated reference builds. Move to ' |
332 'reference_builds/reference_builds and make a change with gcl.') | 392 'reference_builds/reference_builds and make a change with gcl.') |
333 | 393 |
334 if __name__ == '__main__': | 394 if __name__ == '__main__': |
335 sys.exit(main(sys.argv)) | 395 sys.exit(main(sys.argv)) |
OLD | NEW |