OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/python |
| 2 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 import datetime |
| 7 import multiprocessing |
| 8 import optparse |
| 9 import os |
| 10 import sys |
| 11 |
| 12 from chromite.lib import cros_build_lib |
| 13 """ |
| 14 This script is used to upload host prebuilts as well as board BINHOSTS to |
| 15 Google Storage. |
| 16 |
| 17 After a build is successfully uploaded a file is updated with the proper |
| 18 BINHOST version as well as the target board. This file is defined in GIT_FILE |
| 19 |
| 20 |
| 21 To read more about prebuilts/binhost binary packages please refer to: |
| 22 http://sites/chromeos/for-team-members/engineering/releng/prebuilt-binaries-for-
streamlining-the-build-process |
| 23 |
| 24 |
| 25 Example of uploading prebuilt amd64 host files |
| 26 ./prebuilt.py -p /b/cbuild/build -s -u gs://chromeos-prebuilt |
| 27 |
| 28 Example of uploading x86-dogfood binhosts |
| 29 ./prebuilt.py -b x86-dogfood -p /b/cbuild/build/ -u gs://chromeos-prebuilt -g |
| 30 """ |
| 31 |
| 32 VER_FILE = 'src/third_party/chromiumos-overlay/chromeos/config/stable_versions' |
| 33 |
| 34 # as per http://crosbug.com/5855 always filter the below packages |
| 35 _FILTER_PACKAGES = set() |
| 36 _RETRIES = 3 |
| 37 _HOST_PACKAGES_PATH = 'chroot/var/lib/portage/pkgs' |
| 38 _HOST_TARGET = 'amd64' |
| 39 _BOARD_PATH = 'chroot/build/%(board)s' |
| 40 _BOTO_CONFIG = '/home/chrome-bot/external-boto' |
| 41 # board/board-target/version' |
| 42 _GS_BOARD_PATH = 'board/%(board)s/%(version)s/' |
| 43 # We only support amd64 right now |
| 44 _GS_HOST_PATH = 'host/%s' % _HOST_TARGET |
| 45 |
| 46 def UpdateLocalFile(filename, key, value): |
| 47 """Update the key in file with the value passed. |
| 48 File format: |
| 49 key value |
| 50 |
| 51 Args: |
| 52 filename: Name of file to modify. |
| 53 key: The variable key to update. |
| 54 value: Value to write with the key. |
| 55 """ |
| 56 file_fh = open(filename) |
| 57 file_lines = [] |
| 58 found = False |
| 59 for line in file_fh: |
| 60 file_var, file_val = line.split() |
| 61 if file_var == key: |
| 62 found = True |
| 63 print 'Updating %s %s to %s %s' % (file_var, file_val, key, value) |
| 64 file_lines.append('%s %s' % (key, value)) |
| 65 else: |
| 66 file_lines.append('%s %s' % (file_var, file_val)) |
| 67 |
| 68 if not found: |
| 69 file_lines.append('%s %s' % (key, value)) |
| 70 |
| 71 file_fh.close() |
| 72 # write out new file |
| 73 new_file_fh = open(filename, 'w') |
| 74 new_file_fh.write('\n'.join(file_lines)) |
| 75 new_file_fh.close() |
| 76 |
| 77 |
| 78 def RevGitFile(filename, key, value): |
| 79 """Update and push the git file. |
| 80 |
| 81 Args: |
| 82 filename: file to modify that is in a git repo already |
| 83 key: board or host package type e.g. x86-dogfood |
| 84 value: string representing the version of the prebuilt that has been |
| 85 uploaded. |
| 86 """ |
| 87 prebuilt_branch = 'prebuilt_branch' |
| 88 old_cwd = os.getcwd() |
| 89 os.chdir(os.path.dirname(filename)) |
| 90 cros_build_lib.RunCommand('repo start %s .' % prebuilt_branch, shell=True) |
| 91 UpdateLocalFile(filename, key, value) |
| 92 description = 'Update BINHOST key/value %s %s' % (key, value) |
| 93 print description |
| 94 git_ssh_config_cmd = ( |
| 95 'git config url.ssh://git@gitrw.chromium.org:9222.pushinsteadof ' |
| 96 'http://git.chromium.org/git') |
| 97 try: |
| 98 cros_build_lib.RunCommand(git_ssh_config_cmd, shell=True) |
| 99 cros_build_lib.RunCommand('git pull', shell=True) |
| 100 cros_build_lib.RunCommand('git config push.default tracking', shell=True) |
| 101 cros_build_lib.RunCommand('git commit -am "%s"' % description, shell=True) |
| 102 cros_build_lib.RunCommand('git push', shell=True) |
| 103 finally: |
| 104 cros_build_lib.RunCommand('repo abandon %s .' % prebuilt_branch, shell=True) |
| 105 os.chdir(old_cwd) |
| 106 |
| 107 |
| 108 def GetVersion(): |
| 109 """Get the version to put in LATEST and update the git version with.""" |
| 110 return datetime.datetime.now().strftime('%d.%m.%y.%H%M%S') |
| 111 |
| 112 |
| 113 def LoadFilterFile(filter_file): |
| 114 """Load a file with keywords on a per line basis. |
| 115 |
| 116 Args: |
| 117 filter_file: file to load into _FILTER_PACKAGES |
| 118 """ |
| 119 filter_fh = open(filter_file) |
| 120 try: |
| 121 _FILTER_PACKAGES.update([filter.strip() for filter in filter_fh]) |
| 122 finally: |
| 123 filter_fh.close() |
| 124 return _FILTER_PACKAGES |
| 125 |
| 126 |
| 127 def ShouldFilterPackage(file_path): |
| 128 """Skip a particular file if it matches a pattern. |
| 129 |
| 130 Skip any files that machine the list of packages to filter in |
| 131 _FILTER_PACKAGES. |
| 132 |
| 133 Args: |
| 134 file_path: string of a file path to inspect against _FILTER_PACKAGES |
| 135 |
| 136 Returns: |
| 137 True if we should filter the package, |
| 138 False otherwise. |
| 139 """ |
| 140 for name in _FILTER_PACKAGES: |
| 141 if name in file_path: |
| 142 print 'FILTERING %s' % file_path |
| 143 return True |
| 144 |
| 145 return False |
| 146 |
| 147 |
| 148 def _GsUpload(args): |
| 149 """Upload to GS bucket. |
| 150 |
| 151 Args: |
| 152 args: a tuple of two arguments that contains local_file and remote_file. |
| 153 """ |
| 154 (local_file, remote_file) = args |
| 155 if ShouldFilterPackage(local_file): |
| 156 return |
| 157 |
| 158 cmd = 'gsutil cp -a public-read %s %s' % (local_file, remote_file) |
| 159 # TODO(scottz): port to use _Run or similar when it is available in |
| 160 # cros_build_lib. |
| 161 for attempt in range(_RETRIES): |
| 162 try: |
| 163 output = cros_build_lib.RunCommand(cmd, print_cmd=False, shell=True) |
| 164 break |
| 165 except cros_build_lib.RunCommandError: |
| 166 print 'Failed to sync %s -> %s, retrying' % (local_file, remote_file) |
| 167 else: |
| 168 # TODO(scottz): potentially return what failed so we can do something with |
| 169 # with it but for now just print an error. |
| 170 print 'Retry failed uploading %s -> %s, giving up' % (local_file, |
| 171 remote_file) |
| 172 |
| 173 |
| 174 def RemoteUpload(files, pool=10): |
| 175 """Upload to google storage. |
| 176 |
| 177 Create a pool of process and call _GsUpload with the proper arguments. |
| 178 |
| 179 Args: |
| 180 files: dictionary with keys to local files and values to remote path. |
| 181 pool: integer of maximum proesses to have at the same time. |
| 182 """ |
| 183 # TODO(scottz) port this to use _RunManyParallel when it is available in |
| 184 # cros_build_lib |
| 185 pool = multiprocessing.Pool(processes=pool) |
| 186 workers = [] |
| 187 for local_file, remote_path in files.iteritems(): |
| 188 workers.append((local_file, remote_path)) |
| 189 |
| 190 result = pool.map_async(_GsUpload, workers, chunksize=1) |
| 191 while True: |
| 192 try: |
| 193 result.get(60*60) |
| 194 break |
| 195 except multiprocessing.TimeoutError: |
| 196 pass |
| 197 |
| 198 |
| 199 def GenerateUploadDict(local_path, gs_path, strip_str): |
| 200 """Build a dictionary of local remote file key pairs for gsutil to upload. |
| 201 |
| 202 Args: |
| 203 local_path: A path to the file on the local hard drive. |
| 204 gs_path: Path to upload in Google Storage. |
| 205 strip_str: String to remove from the local_path so that the relative |
| 206 file path can be tacked on to the gs_path. |
| 207 |
| 208 Returns: |
| 209 Returns a dictionary of file path/gs_dest_path pairs |
| 210 """ |
| 211 files_to_sync = cros_build_lib.ListFiles(local_path) |
| 212 upload_files = {} |
| 213 for file_path in files_to_sync: |
| 214 filename = file_path.replace(strip_str, '').lstrip('/') |
| 215 gs_file_path = os.path.join(gs_path, filename) |
| 216 upload_files[file_path] = gs_file_path |
| 217 |
| 218 return upload_files |
| 219 |
| 220 |
| 221 def UploadPrebuilt(build_path, bucket, board=None, git_file=None): |
| 222 """Upload Host prebuilt files to Google Storage space. |
| 223 |
| 224 Args: |
| 225 build_path: The path to the root of the chroot. |
| 226 bucket: The Google Storage bucket to upload to. |
| 227 board: The board to upload to Google Storage, if this is None upload |
| 228 host packages. |
| 229 git_file: If set, update this file with a host/version combo, commit and |
| 230 push it. |
| 231 """ |
| 232 version = GetVersion() |
| 233 |
| 234 if not board: |
| 235 # We are uploading host packages |
| 236 # TODO(scottz): eventually add support for different host_targets |
| 237 package_path = os.path.join(build_path, _HOST_PACKAGES_PATH) |
| 238 gs_path = os.path.join(bucket, _GS_HOST_PATH, version) |
| 239 strip_pattern = package_path |
| 240 package_string = _HOST_TARGET |
| 241 else: |
| 242 board_path = os.path.join(build_path, _BOARD_PATH % {'board': board}) |
| 243 package_path = os.path.join(board_path, 'packages') |
| 244 package_string = board |
| 245 strip_pattern = board_path |
| 246 gs_path = os.path.join(bucket, _GS_BOARD_PATH % {'board': board, |
| 247 'version': version}) |
| 248 |
| 249 upload_files = GenerateUploadDict(package_path, gs_path, strip_pattern) |
| 250 |
| 251 print 'Uploading %s' % package_string |
| 252 RemoteUpload(upload_files) |
| 253 |
| 254 if git_file: |
| 255 RevGitFile(git_file, package_string, version) |
| 256 |
| 257 |
| 258 def usage(parser, msg): |
| 259 """Display usage message and parser help then exit with 1.""" |
| 260 print >> sys.stderr, msg |
| 261 parser.print_help() |
| 262 sys.exit(1) |
| 263 |
| 264 |
| 265 def main(): |
| 266 parser = optparse.OptionParser() |
| 267 parser.add_option('-b', '--board', dest='board', default=None, |
| 268 help='Board type that was built on this machine') |
| 269 parser.add_option('-p', '--build-path', dest='build_path', |
| 270 help='Path to the chroot') |
| 271 parser.add_option('-s', '--sync-host', dest='sync_host', |
| 272 default=False, action='store_true', |
| 273 help='Sync host prebuilts') |
| 274 parser.add_option('-g', '--git-sync', dest='git_sync', |
| 275 default=False, action='store_true', |
| 276 help='Enable git version sync (This commits to a repo)') |
| 277 parser.add_option('-u', '--upload', dest='upload', |
| 278 default=None, |
| 279 help='Upload to GS bucket') |
| 280 parser.add_option('-f', '--filter', dest='filter_file', |
| 281 default=None, |
| 282 help='File to use for filtering GS bucket uploads') |
| 283 |
| 284 options, args = parser.parse_args() |
| 285 # Setup boto environment for gsutil to use |
| 286 os.environ['BOTO_CONFIG'] = _BOTO_CONFIG |
| 287 if not options.build_path: |
| 288 usage(parser, 'Error: you need provide a chroot path') |
| 289 |
| 290 if not options.upload: |
| 291 usage(parser, 'Error: you need to provide a gsutil upload bucket -u') |
| 292 |
| 293 if options.filter_file: |
| 294 LoadFilterFile(options.filter_file) |
| 295 |
| 296 git_file = None |
| 297 if options.git_sync: |
| 298 git_file = os.path.join(options.build_path, VER_FILE) |
| 299 |
| 300 if options.sync_host: |
| 301 UploadPrebuilt(options.build_path, options.upload, git_file=git_file) |
| 302 |
| 303 if options.board: |
| 304 UploadPrebuilt(options.build_path, options.upload, board=options.board, |
| 305 git_file=git_file) |
| 306 |
| 307 |
| 308 if __name__ == '__main__': |
| 309 main() |
OLD | NEW |