[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # |
| 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 |
| 5 | # found in the LICENSE file. |
| 6 | |
| 7 | """Updates the Chrome reference builds. |
| 8 | |
[email protected] | 3c01b3ae | 2014-03-04 06:57:59 | [diff] [blame] | 9 | Before running this script, you should first verify that you are authenticated |
| 10 | for SVN. You can do this by running: |
| 11 | $ svn ls svn://svn.chromium.org/chrome/trunk/deps/reference_builds |
| 12 | You may need to get your SVN password from https://chromium-access.appspot.com/. |
[email protected] | acc46302 | 2013-10-19 10:45:15 | [diff] [blame] | 13 | |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 14 | Usage: |
| 15 | $ cd /tmp |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 16 | $ /path/to/update_reference_build.py VERSION # e.g. 37.0.2062.94 |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 17 | $ cd reference_builds/reference_builds |
| 18 | $ gcl change |
| 19 | $ gcl upload <change> |
| 20 | $ gcl commit <change> |
| 21 | """ |
| 22 | |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 23 | import logging |
| 24 | import optparse |
| 25 | import os |
| 26 | import shutil |
| 27 | import subprocess |
| 28 | import sys |
| 29 | import time |
| 30 | import urllib |
| 31 | import urllib2 |
| 32 | import zipfile |
| 33 | |
[email protected] | 1095aa6 | 2013-10-26 00:28:34 | [diff] [blame] | 34 | |
| 35 | # Google storage location (no public web URL's), example: |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 36 | # gs://chrome-unsigned/desktop-*/30.0.1595.0/precise32/chrome-precise32.zip |
| 37 | CHROME_GS_URL_FMT = ('gs://chrome-unsigned/desktop-*/%s/%s/%s') |
[email protected] | acc46302 | 2013-10-19 10:45:15 | [diff] [blame] | 38 | |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 39 | |
| 40 | class BuildUpdater(object): |
[email protected] | acc46302 | 2013-10-19 10:45:15 | [diff] [blame] | 41 | _CHROME_PLATFORM_FILES_MAP = { |
| 42 | 'Win': [ |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 43 | 'chrome-win.zip', |
[email protected] | acc46302 | 2013-10-19 10:45:15 | [diff] [blame] | 44 | ], |
| 45 | 'Mac': [ |
| 46 | 'chrome-mac.zip', |
| 47 | ], |
| 48 | 'Linux': [ |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 49 | 'chrome-precise32.zip', |
[email protected] | acc46302 | 2013-10-19 10:45:15 | [diff] [blame] | 50 | ], |
| 51 | 'Linux_x64': [ |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 52 | 'chrome-precise64.zip', |
[email protected] | acc46302 | 2013-10-19 10:45:15 | [diff] [blame] | 53 | ], |
| 54 | } |
| 55 | |
| 56 | # Map of platform names to gs:// Chrome build names. |
| 57 | _BUILD_PLATFORM_MAP = { |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 58 | 'Linux': 'precise32', |
| 59 | 'Linux_x64': 'precise64', |
[email protected] | acc46302 | 2013-10-19 10:45:15 | [diff] [blame] | 60 | 'Win': 'win', |
| 61 | 'Mac': 'mac', |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 62 | } |
| 63 | |
| 64 | _PLATFORM_DEST_MAP = { |
[email protected] | acc46302 | 2013-10-19 10:45:15 | [diff] [blame] | 65 | 'Linux': 'chrome_linux', |
| 66 | 'Linux_x64': 'chrome_linux64', |
| 67 | 'Win': 'chrome_win', |
| 68 | 'Mac': 'chrome_mac', |
| 69 | } |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 70 | |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 71 | def __init__(self, version, options): |
| 72 | self._version = version |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 73 | self._platforms = options.platforms.split(',') |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 74 | |
| 75 | @staticmethod |
| 76 | def _GetCmdStatusAndOutput(args, cwd=None, shell=False): |
| 77 | """Executes a subprocess and returns its exit code and output. |
| 78 | |
| 79 | Args: |
| 80 | args: A string or a sequence of program arguments. |
| 81 | cwd: If not None, the subprocess's current directory will be changed to |
| 82 | |cwd| before it's executed. |
| 83 | shell: Whether to execute args as a shell command. |
| 84 | |
| 85 | Returns: |
| 86 | The tuple (exit code, output). |
| 87 | """ |
| 88 | logging.info(str(args) + ' ' + (cwd or '')) |
| 89 | p = subprocess.Popen(args=args, cwd=cwd, stdout=subprocess.PIPE, |
| 90 | stderr=subprocess.PIPE, shell=shell) |
| 91 | stdout, stderr = p.communicate() |
| 92 | exit_code = p.returncode |
| 93 | if stderr: |
| 94 | logging.critical(stderr) |
| 95 | logging.info(stdout) |
| 96 | return (exit_code, stdout) |
| 97 | |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 98 | def _GetBuildUrl(self, platform, version, filename): |
[email protected] | 3c01b3ae | 2014-03-04 06:57:59 | [diff] [blame] | 99 | """Returns the URL for fetching one file. |
| 100 | |
| 101 | Args: |
| 102 | platform: Platform name, must be a key in |self._BUILD_PLATFORM_MAP|. |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 103 | version: A Chrome version number, e.g. 30.0.1600.1. |
[email protected] | 3c01b3ae | 2014-03-04 06:57:59 | [diff] [blame] | 104 | filename: Name of the file to fetch. |
| 105 | |
| 106 | Returns: |
| 107 | The URL for fetching a file. This may be a GS or HTTP URL. |
| 108 | """ |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 109 | return CHROME_GS_URL_FMT % ( |
| 110 | version, self._BUILD_PLATFORM_MAP[platform], filename) |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 111 | |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 112 | def _FindBuildVersion(self, platform, version, filename): |
| 113 | """Searches for a version where a filename can be found. |
[email protected] | acc46302 | 2013-10-19 10:45:15 | [diff] [blame] | 114 | |
[email protected] | 3c01b3ae | 2014-03-04 06:57:59 | [diff] [blame] | 115 | Args: |
| 116 | platform: Platform name. |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 117 | version: A Chrome version number, e.g. 30.0.1600.1. |
[email protected] | 3c01b3ae | 2014-03-04 06:57:59 | [diff] [blame] | 118 | filename: Filename to look for. |
| 119 | |
| 120 | Returns: |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 121 | A version where the file could be found, or None. |
[email protected] | 3c01b3ae | 2014-03-04 06:57:59 | [diff] [blame] | 122 | """ |
| 123 | # TODO(shadi): Iterate over official versions to find a valid one. |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 124 | return (version |
| 125 | if self._DoesBuildExist(platform, version, filename) else None) |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 126 | |
[email protected] | 3c01b3ae | 2014-03-04 06:57:59 | [diff] [blame] | 127 | def _DoesBuildExist(self, platform, version, filename): |
| 128 | """Checks whether a file can be found for the given Chrome version. |
| 129 | |
| 130 | Args: |
| 131 | platform: Platform name. |
| 132 | version: Chrome version number, e.g. 30.0.1600.1. |
| 133 | filename: Filename to look for. |
| 134 | |
| 135 | Returns: |
| 136 | True if the file could be found, False otherwise. |
| 137 | """ |
| 138 | url = self._GetBuildUrl(platform, version, filename) |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 139 | return self._DoesGSFileExist(url) |
[email protected] | acc46302 | 2013-10-19 10:45:15 | [diff] [blame] | 140 | |
[email protected] | 1095aa6 | 2013-10-26 00:28:34 | [diff] [blame] | 141 | def _DoesGSFileExist(self, gs_file_name): |
[email protected] | 3c01b3ae | 2014-03-04 06:57:59 | [diff] [blame] | 142 | """Returns True if the GS file can be found, False otherwise.""" |
[email protected] | 1095aa6 | 2013-10-26 00:28:34 | [diff] [blame] | 143 | exit_code = BuildUpdater._GetCmdStatusAndOutput( |
| 144 | ['gsutil', 'ls', gs_file_name])[0] |
[email protected] | acc46302 | 2013-10-19 10:45:15 | [diff] [blame] | 145 | return not exit_code |
| 146 | |
| 147 | def _GetPlatformFiles(self, platform): |
[email protected] | 3c01b3ae | 2014-03-04 06:57:59 | [diff] [blame] | 148 | """Returns a list of filenames to fetch for the given platform.""" |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 149 | return BuildUpdater._CHROME_PLATFORM_FILES_MAP[platform] |
[email protected] | acc46302 | 2013-10-19 10:45:15 | [diff] [blame] | 150 | |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 151 | def _DownloadBuilds(self): |
| 152 | for platform in self._platforms: |
[email protected] | 3c01b3ae | 2014-03-04 06:57:59 | [diff] [blame] | 153 | for filename in self._GetPlatformFiles(platform): |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 154 | output = os.path.join('dl', platform, |
[email protected] | 3c01b3ae | 2014-03-04 06:57:59 | [diff] [blame] | 155 | '%s_%s_%s' % (platform, |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 156 | self._version, |
[email protected] | 3c01b3ae | 2014-03-04 06:57:59 | [diff] [blame] | 157 | filename)) |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 158 | if os.path.exists(output): |
[email protected] | 1095aa6 | 2013-10-26 00:28:34 | [diff] [blame] | 159 | logging.info('%s alread exists, skipping download', output) |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 160 | continue |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 161 | version = self._FindBuildVersion(platform, self._version, filename) |
| 162 | if not version: |
[email protected] | 1095aa6 | 2013-10-26 00:28:34 | [diff] [blame] | 163 | logging.critical('Failed to find %s build for r%s\n', platform, |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 164 | self._version) |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 165 | sys.exit(1) |
| 166 | dirname = os.path.dirname(output) |
| 167 | if dirname and not os.path.exists(dirname): |
| 168 | os.makedirs(dirname) |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 169 | url = self._GetBuildUrl(platform, version, filename) |
[email protected] | acc46302 | 2013-10-19 10:45:15 | [diff] [blame] | 170 | self._DownloadFile(url, output) |
| 171 | |
| 172 | def _DownloadFile(self, url, output): |
[email protected] | 1095aa6 | 2013-10-26 00:28:34 | [diff] [blame] | 173 | logging.info('Downloading %s, saving to %s', url, output) |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 174 | BuildUpdater._GetCmdStatusAndOutput(['gsutil', 'cp', url, output]) |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 175 | |
| 176 | def _FetchSvnRepos(self): |
| 177 | if not os.path.exists('reference_builds'): |
| 178 | os.makedirs('reference_builds') |
| 179 | BuildUpdater._GetCmdStatusAndOutput( |
| 180 | ['gclient', 'config', |
| 181 | 'svn://svn.chromium.org/chrome/trunk/deps/reference_builds'], |
| 182 | 'reference_builds') |
| 183 | BuildUpdater._GetCmdStatusAndOutput( |
| 184 | ['gclient', 'sync'], 'reference_builds') |
| 185 | |
| 186 | def _UnzipFile(self, dl_file, dest_dir): |
[email protected] | 3c01b3ae | 2014-03-04 06:57:59 | [diff] [blame] | 187 | """Unzips a file if it is a zip file. |
| 188 | |
| 189 | Args: |
| 190 | dl_file: The downloaded file to unzip. |
| 191 | dest_dir: The destination directory to unzip to. |
| 192 | |
| 193 | Returns: |
| 194 | True if the file was unzipped. False if it wasn't a zip file. |
| 195 | """ |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 196 | if not zipfile.is_zipfile(dl_file): |
| 197 | return False |
[email protected] | 1095aa6 | 2013-10-26 00:28:34 | [diff] [blame] | 198 | logging.info('Opening %s', dl_file) |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 199 | with zipfile.ZipFile(dl_file, 'r') as z: |
| 200 | for content in z.namelist(): |
| 201 | dest = os.path.join(dest_dir, content[content.find('/')+1:]) |
[email protected] | acc46302 | 2013-10-19 10:45:15 | [diff] [blame] | 202 | # Create dest parent dir if it does not exist. |
| 203 | if not os.path.isdir(os.path.dirname(dest)): |
| 204 | os.makedirs(os.path.dirname(dest)) |
| 205 | # If dest is just a dir listing, do nothing. |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 206 | if not os.path.basename(dest): |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 207 | continue |
[email protected] | b047350 | 2013-11-15 15:54:12 | [diff] [blame] | 208 | if not os.path.isdir(os.path.dirname(dest)): |
| 209 | os.makedirs(os.path.dirname(dest)) |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 210 | with z.open(content) as unzipped_content: |
[email protected] | 1095aa6 | 2013-10-26 00:28:34 | [diff] [blame] | 211 | logging.info('Extracting %s to %s (%s)', content, dest, dl_file) |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 212 | with file(dest, 'wb') as dest_file: |
| 213 | dest_file.write(unzipped_content.read()) |
| 214 | permissions = z.getinfo(content).external_attr >> 16 |
| 215 | if permissions: |
| 216 | os.chmod(dest, permissions) |
| 217 | return True |
| 218 | |
| 219 | def _ClearDir(self, dir): |
| 220 | """Clears all files in |dir| except for hidden files and folders.""" |
| 221 | for root, dirs, files in os.walk(dir): |
| 222 | # Skip hidden files and folders (like .svn and .git). |
| 223 | files = [f for f in files if f[0] != '.'] |
| 224 | dirs[:] = [d for d in dirs if d[0] != '.'] |
| 225 | |
| 226 | for f in files: |
| 227 | os.remove(os.path.join(root, f)) |
| 228 | |
| 229 | def _ExtractBuilds(self): |
| 230 | for platform in self._platforms: |
| 231 | if os.path.exists('tmp_unzip'): |
| 232 | os.path.unlink('tmp_unzip') |
| 233 | dest_dir = os.path.join('reference_builds', 'reference_builds', |
| 234 | BuildUpdater._PLATFORM_DEST_MAP[platform]) |
| 235 | self._ClearDir(dest_dir) |
| 236 | for root, _, dl_files in os.walk(os.path.join('dl', platform)): |
| 237 | for dl_file in dl_files: |
| 238 | dl_file = os.path.join(root, dl_file) |
| 239 | if not self._UnzipFile(dl_file, dest_dir): |
[email protected] | 1095aa6 | 2013-10-26 00:28:34 | [diff] [blame] | 240 | logging.info('Copying %s to %s', dl_file, dest_dir) |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 241 | shutil.copy(dl_file, dest_dir) |
| 242 | |
| 243 | def _SvnAddAndRemove(self): |
| 244 | svn_dir = os.path.join('reference_builds', 'reference_builds') |
[email protected] | 1095aa6 | 2013-10-26 00:28:34 | [diff] [blame] | 245 | # List all changes without ignoring any files. |
| 246 | stat = BuildUpdater._GetCmdStatusAndOutput(['svn', 'stat', '--no-ignore'], |
| 247 | svn_dir)[1] |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 248 | for line in stat.splitlines(): |
| 249 | action, filename = line.split(None, 1) |
[email protected] | 1095aa6 | 2013-10-26 00:28:34 | [diff] [blame] | 250 | # Add new and ignored files. |
| 251 | if action == '?' or action == 'I': |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 252 | BuildUpdater._GetCmdStatusAndOutput( |
| 253 | ['svn', 'add', filename], svn_dir) |
| 254 | elif action == '!': |
| 255 | BuildUpdater._GetCmdStatusAndOutput( |
| 256 | ['svn', 'delete', filename], svn_dir) |
| 257 | filepath = os.path.join(svn_dir, filename) |
| 258 | if not os.path.isdir(filepath) and os.access(filepath, os.X_OK): |
| 259 | BuildUpdater._GetCmdStatusAndOutput( |
| 260 | ['svn', 'propset', 'svn:executable', 'true', filename], svn_dir) |
| 261 | |
| 262 | def DownloadAndUpdateBuilds(self): |
| 263 | self._DownloadBuilds() |
| 264 | self._FetchSvnRepos() |
| 265 | self._ExtractBuilds() |
| 266 | self._SvnAddAndRemove() |
| 267 | |
| 268 | |
| 269 | def ParseOptions(argv): |
| 270 | parser = optparse.OptionParser() |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 271 | parser.set_usage('Usage: %prog VERSION [-p PLATFORMS]') |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 272 | parser.add_option('-p', dest='platforms', |
| 273 | default='Win,Mac,Linux,Linux_x64', |
| 274 | help='Comma separated list of platforms to download ' |
| 275 | '(as defined by the chromium builders).') |
[email protected] | 1095aa6 | 2013-10-26 00:28:34 | [diff] [blame] | 276 | |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 277 | options, args = parser.parse_args(argv) |
| 278 | if len(args) != 2: |
| 279 | parser.print_help() |
[email protected] | acc46302 | 2013-10-19 10:45:15 | [diff] [blame] | 280 | sys.exit(1) |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 281 | version = args[1] |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 282 | |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 283 | return version, options |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 284 | |
| 285 | |
| 286 | def main(argv): |
| 287 | logging.getLogger().setLevel(logging.DEBUG) |
[email protected] | 3c70abf6 | 2014-08-22 23:55:11 | [diff] [blame] | 288 | version, options = ParseOptions(argv) |
| 289 | b = BuildUpdater(version, options) |
[email protected] | 4d8532f | 2013-05-03 16:30:43 | [diff] [blame] | 290 | b.DownloadAndUpdateBuilds() |
| 291 | logging.info('Successfully updated reference builds. Move to ' |
| 292 | 'reference_builds/reference_builds and make a change with gcl.') |
| 293 | |
| 294 | if __name__ == '__main__': |
| 295 | sys.exit(main(sys.argv)) |