Chromium Code Reviews| 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 Use -r option to update a Chromium reference build, or -b option for Chrome |
| 10 official builds. | 10 official builds. |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 27 import time | 27 import time |
| 28 import urllib | 28 import urllib |
| 29 import urllib2 | 29 import urllib2 |
| 30 import zipfile | 30 import zipfile |
| 31 | 31 |
| 32 # Example chromium build location: | 32 # Example chromium build location: |
| 33 # gs://chromium-browser-snapshots/Linux/228977/chrome-linux.zip | 33 # gs://chromium-browser-snapshots/Linux/228977/chrome-linux.zip |
| 34 CHROMIUM_URL_FMT = ('http://commondatastorage.googleapis.com/' | 34 CHROMIUM_URL_FMT = ('http://commondatastorage.googleapis.com/' |
| 35 'chromium-browser-snapshots/%s/%s/%s') | 35 'chromium-browser-snapshots/%s/%s/%s') |
| 36 | 36 |
| 37 # Example Chrome build location (no public wed URL's): | 37 # Chrome official build storage |
| 38 # https://wiki.corp.google.com/twiki/bin/view/Main/ChromeOfficialBuilds | |
| 39 | |
| 40 # Internal Google archive of official Chrome builds, example: | |
| 41 # http://master.chrome.corp.google.com/official_builds/32.0.1677.0/precise32bit/ | |
| 42 CHROME_INTERNAL_URL_FMT = ('http://master.chrome.corp.google.com/' | |
| 43 'official_builds/%s/%s/%s') | |
|
tonyg
2013/10/21 19:20:06
I'm not sure we should check in a corp url. Can we
shadi
2013/10/21 19:53:53
Done.
| |
| 44 | |
| 45 # Google storage location (no public web URL's), example: | |
| 38 # gs://chrome-archive/30/30.0.1595.0/precise32bit/chrome-precise32bit.zip | 46 # gs://chrome-archive/30/30.0.1595.0/precise32bit/chrome-precise32bit.zip |
| 39 CHROME_URL_FMT = ('gs://chrome-archive/%s/%s/%s/%s') | 47 CHROME_GS_URL_FMT = ('gs://chrome-archive/%s/%s/%s/%s') |
| 40 | 48 |
| 41 | 49 |
| 42 class BuildUpdater(object): | 50 class BuildUpdater(object): |
| 43 _PLATFORM_FILES_MAP = { | 51 _PLATFORM_FILES_MAP = { |
| 44 'Win': [ | 52 'Win': [ |
| 45 'chrome-win32.zip', | 53 'chrome-win32.zip', |
| 46 'chrome-win32-syms.zip', | 54 'chrome-win32-syms.zip', |
| 47 ], | 55 ], |
| 48 'Mac': [ | 56 'Mac': [ |
| 49 'chrome-mac.zip', | 57 'chrome-mac.zip', |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 84 'Linux': 'chrome_linux', | 92 'Linux': 'chrome_linux', |
| 85 'Linux_x64': 'chrome_linux64', | 93 'Linux_x64': 'chrome_linux64', |
| 86 'Win': 'chrome_win', | 94 'Win': 'chrome_win', |
| 87 'Mac': 'chrome_mac', | 95 'Mac': 'chrome_mac', |
| 88 } | 96 } |
| 89 | 97 |
| 90 def __init__(self, options): | 98 def __init__(self, options): |
| 91 self._platforms = options.platforms.split(',') | 99 self._platforms = options.platforms.split(',') |
| 92 self._revision = options.build_number or int(options.revision) | 100 self._revision = options.build_number or int(options.revision) |
| 93 self._use_build_number = bool(options.build_number) | 101 self._use_build_number = bool(options.build_number) |
| 102 self._use_gs = options.use_gs | |
| 94 | 103 |
| 95 @staticmethod | 104 @staticmethod |
| 96 def _GetCmdStatusAndOutput(args, cwd=None, shell=False): | 105 def _GetCmdStatusAndOutput(args, cwd=None, shell=False): |
| 97 """Executes a subprocess and returns its exit code and output. | 106 """Executes a subprocess and returns its exit code and output. |
| 98 | 107 |
| 99 Args: | 108 Args: |
| 100 args: A string or a sequence of program arguments. | 109 args: A string or a sequence of program arguments. |
| 101 cwd: If not None, the subprocess's current directory will be changed to | 110 cwd: If not None, the subprocess's current directory will be changed to |
| 102 |cwd| before it's executed. | 111 |cwd| before it's executed. |
| 103 shell: Whether to execute args as a shell command. | 112 shell: Whether to execute args as a shell command. |
| 104 | 113 |
| 105 Returns: | 114 Returns: |
| 106 The tuple (exit code, output). | 115 The tuple (exit code, output). |
| 107 """ | 116 """ |
| 108 logging.info(str(args) + ' ' + (cwd or '')) | 117 logging.info(str(args) + ' ' + (cwd or '')) |
| 109 p = subprocess.Popen(args=args, cwd=cwd, stdout=subprocess.PIPE, | 118 p = subprocess.Popen(args=args, cwd=cwd, stdout=subprocess.PIPE, |
| 110 stderr=subprocess.PIPE, shell=shell) | 119 stderr=subprocess.PIPE, shell=shell) |
| 111 stdout, stderr = p.communicate() | 120 stdout, stderr = p.communicate() |
| 112 exit_code = p.returncode | 121 exit_code = p.returncode |
| 113 if stderr: | 122 if stderr: |
| 114 logging.critical(stderr) | 123 logging.critical(stderr) |
| 115 logging.info(stdout) | 124 logging.info(stdout) |
| 116 return (exit_code, stdout) | 125 return (exit_code, stdout) |
| 117 | 126 |
| 118 def _GetBuildUrl(self, platform, revision, filename): | 127 def _GetBuildUrl(self, platform, revision, filename): |
| 119 if self._use_build_number: | 128 if self._use_build_number: |
| 120 release = revision[:revision.find('.')] | 129 # Chrome Google storage bucket. |
| 121 return (CHROME_URL_FMT % | 130 if self._use_gs: |
| 122 (release, revision, self._BUILD_PLATFORM_MAP[platform], filename)) | 131 release = revision[:revision.find('.')] |
| 132 return (CHROME_GS_URL_FMT % ( | |
| 133 release, | |
| 134 revision, | |
| 135 self._BUILD_PLATFORM_MAP[platform], | |
| 136 filename)) | |
| 137 # Chrome internal archive. | |
| 138 return (CHROME_INTERNAL_URL_FMT % ( | |
| 139 revision, | |
| 140 self._BUILD_PLATFORM_MAP[platform], | |
| 141 filename)) | |
| 142 # Chromium archive. | |
| 123 return CHROMIUM_URL_FMT % (urllib.quote_plus(platform), revision, filename) | 143 return CHROMIUM_URL_FMT % (urllib.quote_plus(platform), revision, filename) |
| 124 | 144 |
| 125 def _FindBuildRevision(self, platform, revision, filename): | 145 def _FindBuildRevision(self, platform, revision, filename): |
| 126 # TODO(shadi): Iterate over build numbers to find a valid one. | 146 # TODO(shadi): Iterate over build numbers to find a valid one. |
| 127 if self._use_build_number: | 147 if self._use_build_number: |
| 128 return (revision | 148 return (revision |
| 129 if self._DoesChromeBuildExist(platform, revision, filename) else | 149 if self._DoesBuildExist(platform, revision, filename) else None) |
| 130 None) | |
| 131 | 150 |
| 132 MAX_REVISIONS_PER_BUILD = 100 | 151 MAX_REVISIONS_PER_BUILD = 100 |
| 133 for revision_guess in xrange(revision, revision + MAX_REVISIONS_PER_BUILD): | 152 for revision_guess in xrange(revision, revision + MAX_REVISIONS_PER_BUILD): |
| 134 if self._DoesChromiumBuildExist(platform, revision_guess, filename): | 153 if self._DoesBuildExist(platform, revision_guess, filename): |
| 135 return revision_guess | 154 return revision_guess |
| 136 else: | 155 else: |
| 137 time.sleep(.1) | 156 time.sleep(.1) |
| 138 return None | 157 return None |
| 139 | 158 |
| 140 def _DoesChromiumBuildExist(self, platform, build_number, filename): | 159 def _DoesBuildExist(self, platform, build_number, filename): |
| 141 url = self._GetBuildUrl(platform, build_number, filename) | 160 url = self._GetBuildUrl(platform, build_number, filename) |
| 161 if self._use_gs: | |
| 162 return self._DoesGSFileExist(url) | |
| 163 | |
| 142 r = urllib2.Request(url) | 164 r = urllib2.Request(url) |
| 143 r.get_method = lambda: 'HEAD' | 165 r.get_method = lambda: 'HEAD' |
| 144 try: | 166 try: |
| 145 urllib2.urlopen(r) | 167 urllib2.urlopen(r) |
| 146 return True | 168 return True |
| 147 except urllib2.HTTPError, err: | 169 except urllib2.HTTPError, err: |
| 148 if err.code == 404: | 170 if err.code == 404: |
| 149 return False | 171 return False |
| 150 | 172 |
| 151 def _DoesChromeBuildExist(self, platform, build_number, filename): | 173 def _DoesGSFileExist(self, gs_file_name): |
| 152 release = build_number[:build_number.find('.')] | |
| 153 gs_file = (CHROME_URL_FMT % | |
| 154 (release, | |
| 155 build_number, | |
| 156 self._BUILD_PLATFORM_MAP[platform], | |
| 157 filename)) | |
| 158 (exit_code, stdout) = BuildUpdater._GetCmdStatusAndOutput( | 174 (exit_code, stdout) = BuildUpdater._GetCmdStatusAndOutput( |
| 159 ['gsutil', 'ls', gs_file]) | 175 ['gsutil', 'ls', gs_file_name]) |
| 160 | |
| 161 return not exit_code | 176 return not exit_code |
| 162 | 177 |
| 163 def _GetPlatformFiles(self, platform): | 178 def _GetPlatformFiles(self, platform): |
| 164 if self._use_build_number: | 179 if self._use_build_number: |
| 165 return BuildUpdater._CHROME_PLATFORM_FILES_MAP[platform] | 180 return BuildUpdater._CHROME_PLATFORM_FILES_MAP[platform] |
| 166 return BuildUpdater._PLATFORM_FILES_MAP[platform] | 181 return BuildUpdater._PLATFORM_FILES_MAP[platform] |
| 167 | 182 |
| 168 def _DownloadBuilds(self): | 183 def _DownloadBuilds(self): |
| 169 for platform in self._platforms: | 184 for platform in self._platforms: |
| 170 for f in self._GetPlatformFiles(platform): | 185 for f in self._GetPlatformFiles(platform): |
| 171 output = os.path.join('dl', platform, | 186 output = os.path.join('dl', platform, |
| 172 '%s_%s_%s' % (platform, self._revision, f)) | 187 '%s_%s_%s' % (platform, self._revision, f)) |
| 173 if os.path.exists(output): | 188 if os.path.exists(output): |
| 174 logging.info('%s alread exists, skipping download' % output) | 189 logging.info('%s alread exists, skipping download', output) |
| 175 continue | 190 continue |
| 176 build_revision = self._FindBuildRevision(platform, self._revision, f) | 191 build_revision = self._FindBuildRevision(platform, self._revision, f) |
| 177 if not build_revision: | 192 if not build_revision: |
| 178 logging.critical('Failed to find %s build for r%s\n' % ( | 193 logging.critical('Failed to find %s build for r%s\n', platform, |
| 179 platform, self._revision)) | 194 self._revision) |
| 180 sys.exit(1) | 195 sys.exit(1) |
| 181 dirname = os.path.dirname(output) | 196 dirname = os.path.dirname(output) |
| 182 if dirname and not os.path.exists(dirname): | 197 if dirname and not os.path.exists(dirname): |
| 183 os.makedirs(dirname) | 198 os.makedirs(dirname) |
| 184 url = self._GetBuildUrl(platform, build_revision, f) | 199 url = self._GetBuildUrl(platform, build_revision, f) |
| 185 self._DownloadFile(url, output) | 200 self._DownloadFile(url, output) |
| 186 | 201 |
| 187 def _DownloadFile(self, url, output): | 202 def _DownloadFile(self, url, output): |
| 188 logging.info('Downloading %s, saving to %s' % (url, output)) | 203 logging.info('Downloading %s, saving to %s', url, output) |
| 189 if self._use_build_number: | 204 if self._use_build_number and self._use_gs: |
| 190 BuildUpdater._GetCmdStatusAndOutput(['gsutil', 'cp', url, output]) | 205 BuildUpdater._GetCmdStatusAndOutput(['gsutil', 'cp', url, output]) |
| 191 else: | 206 else: |
| 192 r = urllib2.urlopen(url) | 207 r = urllib2.urlopen(url) |
| 193 with file(output, 'wb') as f: | 208 with file(output, 'wb') as f: |
| 194 f.write(r.read()) | 209 f.write(r.read()) |
| 195 | 210 |
| 196 def _FetchSvnRepos(self): | 211 def _FetchSvnRepos(self): |
| 197 if not os.path.exists('reference_builds'): | 212 if not os.path.exists('reference_builds'): |
| 198 os.makedirs('reference_builds') | 213 os.makedirs('reference_builds') |
| 199 BuildUpdater._GetCmdStatusAndOutput( | 214 BuildUpdater._GetCmdStatusAndOutput( |
| 200 ['gclient', 'config', | 215 ['gclient', 'config', |
| 201 'svn://svn.chromium.org/chrome/trunk/deps/reference_builds'], | 216 'svn://svn.chromium.org/chrome/trunk/deps/reference_builds'], |
| 202 'reference_builds') | 217 'reference_builds') |
| 203 BuildUpdater._GetCmdStatusAndOutput( | 218 BuildUpdater._GetCmdStatusAndOutput( |
| 204 ['gclient', 'sync'], 'reference_builds') | 219 ['gclient', 'sync'], 'reference_builds') |
| 205 | 220 |
| 206 def _UnzipFile(self, dl_file, dest_dir): | 221 def _UnzipFile(self, dl_file, dest_dir): |
| 207 if not zipfile.is_zipfile(dl_file): | 222 if not zipfile.is_zipfile(dl_file): |
| 208 return False | 223 return False |
| 209 logging.info('Opening %s' % dl_file) | 224 logging.info('Opening %s', dl_file) |
| 210 with zipfile.ZipFile(dl_file, 'r') as z: | 225 with zipfile.ZipFile(dl_file, 'r') as z: |
| 211 for content in z.namelist(): | 226 for content in z.namelist(): |
| 212 dest = os.path.join(dest_dir, content[content.find('/')+1:]) | 227 dest = os.path.join(dest_dir, content[content.find('/')+1:]) |
| 213 # Create dest parent dir if it does not exist. | 228 # Create dest parent dir if it does not exist. |
| 214 if not os.path.isdir(os.path.dirname(dest)): | 229 if not os.path.isdir(os.path.dirname(dest)): |
| 215 os.makedirs(os.path.dirname(dest)) | 230 os.makedirs(os.path.dirname(dest)) |
| 216 # If dest is just a dir listing, do nothing. | 231 # If dest is just a dir listing, do nothing. |
| 217 if not os.path.basename(dest): | 232 if not os.path.basename(dest): |
| 218 continue | 233 continue |
| 219 with z.open(content) as unzipped_content: | 234 with z.open(content) as unzipped_content: |
| 220 logging.info('Extracting %s to %s (%s)' % (content, dest, dl_file)) | 235 logging.info('Extracting %s to %s (%s)', content, dest, dl_file) |
| 221 with file(dest, 'wb') as dest_file: | 236 with file(dest, 'wb') as dest_file: |
| 222 dest_file.write(unzipped_content.read()) | 237 dest_file.write(unzipped_content.read()) |
| 223 permissions = z.getinfo(content).external_attr >> 16 | 238 permissions = z.getinfo(content).external_attr >> 16 |
| 224 if permissions: | 239 if permissions: |
| 225 os.chmod(dest, permissions) | 240 os.chmod(dest, permissions) |
| 226 return True | 241 return True |
| 227 | 242 |
| 228 def _ClearDir(self, dir): | 243 def _ClearDir(self, dir): |
| 229 """Clears all files in |dir| except for hidden files and folders.""" | 244 """Clears all files in |dir| except for hidden files and folders.""" |
| 230 for root, dirs, files in os.walk(dir): | 245 for root, dirs, files in os.walk(dir): |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 271 self._ExtractBuilds() | 286 self._ExtractBuilds() |
| 272 self._SvnAddAndRemove() | 287 self._SvnAddAndRemove() |
| 273 | 288 |
| 274 | 289 |
| 275 def ParseOptions(argv): | 290 def ParseOptions(argv): |
| 276 parser = optparse.OptionParser() | 291 parser = optparse.OptionParser() |
| 277 usage = 'usage: %prog <options>' | 292 usage = 'usage: %prog <options>' |
| 278 parser.set_usage(usage) | 293 parser.set_usage(usage) |
| 279 parser.add_option('-b', dest='build_number', | 294 parser.add_option('-b', dest='build_number', |
| 280 help='Chrome official build number to pick up.') | 295 help='Chrome official build number to pick up.') |
| 281 parser.add_option('-r', dest='revision', | 296 parser.add_option('--gs', dest='use_gs', action='store_true', default=False, |
| 282 help='Revision to pick up.') | 297 help='Use Google storage for official builds. Used with -b ' |
| 298 'option. Default is false (i.e. use internal storage.') | |
| 283 parser.add_option('-p', dest='platforms', | 299 parser.add_option('-p', dest='platforms', |
| 284 default='Win,Mac,Linux,Linux_x64', | 300 default='Win,Mac,Linux,Linux_x64', |
| 285 help='Comma separated list of platforms to download ' | 301 help='Comma separated list of platforms to download ' |
| 286 '(as defined by the chromium builders).') | 302 '(as defined by the chromium builders).') |
| 303 parser.add_option('-r', dest='revision', | |
| 304 help='Revision to pick up.') | |
| 305 | |
| 287 (options, _) = parser.parse_args(argv) | 306 (options, _) = parser.parse_args(argv) |
| 288 if not options.revision and not options.build_number: | 307 if not options.revision and not options.build_number: |
| 289 logging.critical('Must specify either -r or -b.\n') | 308 logging.critical('Must specify either -r or -b.\n') |
| 290 sys.exit(1) | 309 sys.exit(1) |
| 291 if options.revision and options.build_number: | 310 if options.revision and options.build_number: |
| 292 logging.critical('Must specify either -r or -b but not both.\n') | 311 logging.critical('Must specify either -r or -b but not both.\n') |
| 293 sys.exit(1) | 312 sys.exit(1) |
| 313 if options.use_gs and not options.build_number: | |
| 314 logging.critical('Can only use --gs with -b option.\n') | |
| 315 sys.exit(1) | |
| 294 | 316 |
| 295 return options | 317 return options |
| 296 | 318 |
| 297 | 319 |
| 298 def main(argv): | 320 def main(argv): |
| 299 logging.getLogger().setLevel(logging.DEBUG) | 321 logging.getLogger().setLevel(logging.DEBUG) |
| 300 options = ParseOptions(argv) | 322 options = ParseOptions(argv) |
| 301 b = BuildUpdater(options) | 323 b = BuildUpdater(options) |
| 302 b.DownloadAndUpdateBuilds() | 324 b.DownloadAndUpdateBuilds() |
| 303 logging.info('Successfully updated reference builds. Move to ' | 325 logging.info('Successfully updated reference builds. Move to ' |
| 304 'reference_builds/reference_builds and make a change with gcl.') | 326 'reference_builds/reference_builds and make a change with gcl.') |
| 305 | 327 |
| 306 if __name__ == '__main__': | 328 if __name__ == '__main__': |
| 307 sys.exit(main(sys.argv)) | 329 sys.exit(main(sys.argv)) |
| OLD | NEW |