blob: 8469648c5054e84f210b4b50256644424d2f7671 [file] [log] [blame]
David James8c846492011-01-25 17:07:29 -08001#!/usr/bin/python
Chris Sosac13bba52011-05-24 15:14:09 -07002# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
David James8c846492011-01-25 17:07:29 -08003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import datetime
7import multiprocessing
8import optparse
9import os
10import re
11import sys
12import tempfile
David James8c846492011-01-25 17:07:29 -080013
Chris Sosa471532a2011-02-01 15:10:06 -080014if __name__ == '__main__':
15 import constants
16 sys.path.append(constants.SOURCE_ROOT)
17
David James8c846492011-01-25 17:07:29 -080018from chromite.lib import cros_build_lib
Chris Sosac13bba52011-05-24 15:14:09 -070019from chromite.lib.binpkg import (GrabLocalPackageIndex, GrabRemotePackageIndex)
David James8c846492011-01-25 17:07:29 -080020"""
21This script is used to upload host prebuilts as well as board BINHOSTS.
22
23If the URL starts with 'gs://', we upload using gsutil to Google Storage.
24Otherwise, rsync is used.
25
26After a build is successfully uploaded a file is updated with the proper
27BINHOST version as well as the target board. This file is defined in GIT_FILE
28
29
30To read more about prebuilts/binhost binary packages please refer to:
31http://sites/chromeos/for-team-members/engineering/releng/prebuilt-binaries-for-streamlining-the-build-process
32
33
34Example of uploading prebuilt amd64 host files to Google Storage:
35./prebuilt.py -p /b/cbuild/build -s -u gs://chromeos-prebuilt
36
37Example of uploading x86-dogfood binhosts to Google Storage:
38./prebuilt.py -b x86-dogfood -p /b/cbuild/build/ -u gs://chromeos-prebuilt -g
39
40Example of uploading prebuilt amd64 host files using rsync:
41./prebuilt.py -p /b/cbuild/build -s -u codf30.jail:/tmp
42"""
43
David James8c846492011-01-25 17:07:29 -080044_RETRIES = 3
45_GSUTIL_BIN = '/b/build/third_party/gsutil/gsutil'
46_HOST_PACKAGES_PATH = 'chroot/var/lib/portage/pkgs'
David James05bcb2b2011-02-09 09:25:47 -080047_CATEGORIES_PATH = 'chroot/etc/portage/categories'
David James615e5b52011-06-03 11:10:15 -070048_PYM_PATH = 'chroot/usr/lib/portage/pym'
David James8c846492011-01-25 17:07:29 -080049_HOST_TARGET = 'amd64'
50_BOARD_PATH = 'chroot/build/%(board)s'
David James8fa34ea2011-04-15 13:00:20 -070051# board/board-target/version/'
52_REL_BOARD_PATH = 'board/%(board)s/%(version)s'
53# host/host-target/version/'
54_REL_HOST_PATH = 'host/%(target)s/%(version)s'
David James8c846492011-01-25 17:07:29 -080055# Private overlays to look at for builds to filter
56# relative to build path
57_PRIVATE_OVERLAY_DIR = 'src/private-overlays'
Scott Zawalskiab1bed32011-03-16 15:24:24 -070058_GOOGLESTORAGE_ACL_FILE = 'googlestorage_acl.xml'
David James8c846492011-01-25 17:07:29 -080059_BINHOST_BASE_URL = 'http://commondatastorage.googleapis.com/chromeos-prebuilt'
60_PREBUILT_BASE_DIR = 'src/third_party/chromiumos-overlay/chromeos/config/'
61# Created in the event of new host targets becoming available
62_PREBUILT_MAKE_CONF = {'amd64': os.path.join(_PREBUILT_BASE_DIR,
63 'make.conf.amd64-host')}
64_BINHOST_CONF_DIR = 'src/third_party/chromiumos-overlay/chromeos/binhost'
65
66
David James8c846492011-01-25 17:07:29 -080067class UploadFailed(Exception):
68 """Raised when one of the files uploaded failed."""
69 pass
70
71class UnknownBoardFormat(Exception):
72 """Raised when a function finds an unknown board format."""
73 pass
74
David James8c846492011-01-25 17:07:29 -080075
76def UpdateLocalFile(filename, value, key='PORTAGE_BINHOST'):
77 """Update the key in file with the value passed.
78 File format:
79 key="value"
80 Note quotes are added automatically
81
82 Args:
83 filename: Name of file to modify.
84 value: Value to write with the key.
85 key: The variable key to update. (Default: PORTAGE_BINHOST)
86 """
87 if os.path.exists(filename):
88 file_fh = open(filename)
89 else:
90 file_fh = open(filename, 'w+')
91 file_lines = []
92 found = False
93 keyval_str = '%(key)s=%(value)s'
94 for line in file_fh:
95 # Strip newlines from end of line. We already add newlines below.
96 line = line.rstrip("\n")
97
98 if len(line.split('=')) != 2:
99 # Skip any line that doesn't fit key=val.
100 file_lines.append(line)
101 continue
102
103 file_var, file_val = line.split('=')
104 if file_var == key:
105 found = True
106 print 'Updating %s=%s to %s="%s"' % (file_var, file_val, key, value)
107 value = '"%s"' % value
108 file_lines.append(keyval_str % {'key': key, 'value': value})
109 else:
110 file_lines.append(keyval_str % {'key': file_var, 'value': file_val})
111
112 if not found:
113 file_lines.append(keyval_str % {'key': key, 'value': value})
114
115 file_fh.close()
116 # write out new file
117 new_file_fh = open(filename, 'w')
118 new_file_fh.write('\n'.join(file_lines) + '\n')
119 new_file_fh.close()
120
121
David James27fa7d12011-06-29 17:24:14 -0700122def RevGitFile(filename, value, retries=5, key='PORTAGE_BINHOST', dryrun=False):
David James8c846492011-01-25 17:07:29 -0800123 """Update and push the git file.
124
125 Args:
126 filename: file to modify that is in a git repo already
127 value: string representing the version of the prebuilt that has been
128 uploaded.
129 retries: The number of times to retry before giving up, default: 5
130 key: The variable key to update in the git file.
131 (Default: PORTAGE_BINHOST)
132 """
133 prebuilt_branch = 'prebuilt_branch'
David James1b6e67a2011-05-19 21:32:38 -0700134 cwd = os.path.abspath(os.path.dirname(filename))
135 commit = cros_build_lib.RunCommand(['git', 'rev-parse', 'HEAD'], cwd=cwd,
Peter Mayofe0e6872011-04-20 00:52:08 -0400136 redirect_stdout=True).output.rstrip()
Peter Mayo193f68f2011-04-19 19:08:21 -0400137 git_ssh_config_cmd = [
138 'git',
139 'config',
Chris Sosac13bba52011-05-24 15:14:09 -0700140 'url.ssh://gerrit.chromium.org:29418.pushinsteadof',
David James1b6e67a2011-05-19 21:32:38 -0700141 'http://git.chromium.org']
142 cros_build_lib.RunCommand(git_ssh_config_cmd, cwd=cwd)
143 cros_build_lib.RunCommand(['git', 'remote', 'update'], cwd=cwd)
144 cros_build_lib.RunCommand(['repo', 'start', prebuilt_branch, '.'], cwd=cwd)
David James6181b892011-06-08 16:45:07 -0700145
146 # We want to push our changes to this file to tip of tree, so we should
147 # make sure the branch is at tip of tree before we apply our change.
148 push_branch = '%s/%s' % cros_build_lib.GetPushBranch(prebuilt_branch, cwd)
149 cros_build_lib.RunCommand(['git', 'reset', '--hard', push_branch], cwd=cwd)
150
David James8c846492011-01-25 17:07:29 -0800151 description = 'Update %s="%s" in %s' % (key, value, filename)
152 print description
153 try:
154 UpdateLocalFile(filename, value, key)
David James1b6e67a2011-05-19 21:32:38 -0700155 cros_build_lib.RunCommand(['git', 'config', 'push.default', 'tracking'],
156 cwd=cwd)
157 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
158 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James27fa7d12011-06-29 17:24:14 -0700159 cros_build_lib.GitPushWithRetry(prebuilt_branch, cwd=cwd, dryrun=dryrun)
David James8c846492011-01-25 17:07:29 -0800160 finally:
David James1b6e67a2011-05-19 21:32:38 -0700161 cros_build_lib.RunCommand(['git', 'checkout', commit], cwd=cwd)
162 cros_build_lib.RunCommand(['repo', 'abandon', 'prebuilt_branch', '.'],
163 cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800164
165
166def GetVersion():
167 """Get the version to put in LATEST and update the git version with."""
168 return datetime.datetime.now().strftime('%d.%m.%y.%H%M%S')
169
170
Peter Mayo193f68f2011-04-19 19:08:21 -0400171def _RetryRun(cmd, print_cmd=True, cwd=None):
David James8c846492011-01-25 17:07:29 -0800172 """Run the specified command, retrying if necessary.
173
174 Args:
175 cmd: The command to run.
176 print_cmd: Whether to print out the cmd.
177 shell: Whether to treat the command as a shell.
178 cwd: Working directory to run command in.
179
180 Returns:
181 True if the command succeeded. Otherwise, returns False.
182 """
183
184 # TODO(scottz): port to use _Run or similar when it is available in
185 # cros_build_lib.
Chris Sosa58669192011-06-30 12:45:03 -0700186 for unused_attempt in range(_RETRIES):
David James8c846492011-01-25 17:07:29 -0800187 try:
Chris Sosa58669192011-06-30 12:45:03 -0700188 cros_build_lib.RunCommand(cmd, print_cmd=print_cmd, cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800189 return True
190 except cros_build_lib.RunCommandError:
Peter Mayo193f68f2011-04-19 19:08:21 -0400191 print 'Failed to run %r' % cmd
David James8c846492011-01-25 17:07:29 -0800192 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400193 print 'Retry failed run %r, giving up' % cmd
David James8c846492011-01-25 17:07:29 -0800194 return False
195
196
197def _GsUpload(args):
198 """Upload to GS bucket.
199
200 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800201 args: a tuple of three arguments that contains local_file, remote_file, and
202 the acl used for uploading the file.
David James8c846492011-01-25 17:07:29 -0800203
204 Returns:
205 Return the arg tuple of two if the upload failed
206 """
David Jamesfd0b0852011-02-23 11:15:36 -0800207 (local_file, remote_file, acl) = args
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700208 CANNED_ACLS = ['public-read', 'private', 'bucket-owner-read',
209 'authenticated-read', 'bucket-owner-full-control',
210 'public-read-write']
211 acl_cmd = None
212 if acl in CANNED_ACLS:
Peter Mayo193f68f2011-04-19 19:08:21 -0400213 cmd = [_GSUTIL_BIN, 'cp', '-a', acl, local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700214 else:
215 # For private uploads we assume that the overlay board is set up properly
216 # and a googlestore_acl.xml is present, if not this script errors
Peter Mayo193f68f2011-04-19 19:08:21 -0400217 cmd = [_GSUTIL_BIN, 'cp', '-a', 'private', local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700218 if not os.path.exists(acl):
219 print >> sys.stderr, ('You are specifying either a file that does not '
220 'exist or an unknown canned acl: %s. Aborting '
221 'upload') % acl
222 # emulate the failing of an upload since we are not uploading the file
223 return (local_file, remote_file)
David James8c846492011-01-25 17:07:29 -0800224
Peter Mayo193f68f2011-04-19 19:08:21 -0400225 acl_cmd = [_GSUTIL_BIN, 'setacl', acl, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700226
Peter Mayo193f68f2011-04-19 19:08:21 -0400227 if not _RetryRun(cmd, print_cmd=False):
David James8c846492011-01-25 17:07:29 -0800228 return (local_file, remote_file)
229
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700230 if acl_cmd:
231 # Apply the passed in ACL xml file to the uploaded object.
Peter Mayo193f68f2011-04-19 19:08:21 -0400232 _RetryRun(acl_cmd, print_cmd=False)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700233
234
David Jamesfd0b0852011-02-23 11:15:36 -0800235def RemoteUpload(acl, files, pool=10):
David James8c846492011-01-25 17:07:29 -0800236 """Upload to google storage.
237
238 Create a pool of process and call _GsUpload with the proper arguments.
239
240 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800241 acl: The canned acl used for uploading. acl can be one of: "public-read",
242 "public-read-write", "authenticated-read", "bucket-owner-read",
243 "bucket-owner-full-control", or "private".
David James8c846492011-01-25 17:07:29 -0800244 files: dictionary with keys to local files and values to remote path.
245 pool: integer of maximum proesses to have at the same time.
246
247 Returns:
248 Return a set of tuple arguments of the failed uploads
249 """
250 # TODO(scottz) port this to use _RunManyParallel when it is available in
251 # cros_build_lib
252 pool = multiprocessing.Pool(processes=pool)
253 workers = []
254 for local_file, remote_path in files.iteritems():
David Jamesfd0b0852011-02-23 11:15:36 -0800255 workers.append((local_file, remote_path, acl))
David James8c846492011-01-25 17:07:29 -0800256
257 result = pool.map_async(_GsUpload, workers, chunksize=1)
258 while True:
259 try:
Chris Sosa471532a2011-02-01 15:10:06 -0800260 return set(result.get(60 * 60))
David James8c846492011-01-25 17:07:29 -0800261 except multiprocessing.TimeoutError:
262 pass
263
264
265def GenerateUploadDict(base_local_path, base_remote_path, pkgs):
266 """Build a dictionary of local remote file key pairs to upload.
267
268 Args:
269 base_local_path: The base path to the files on the local hard drive.
270 remote_path: The base path to the remote paths.
271 pkgs: The packages to upload.
272
273 Returns:
274 Returns a dictionary of local_path/remote_path pairs
275 """
276 upload_files = {}
277 for pkg in pkgs:
278 suffix = pkg['CPV'] + '.tbz2'
279 local_path = os.path.join(base_local_path, suffix)
280 assert os.path.exists(local_path)
281 remote_path = '%s/%s' % (base_remote_path.rstrip('/'), suffix)
282 upload_files[local_path] = remote_path
283
284 return upload_files
285
286def GetBoardPathFromCrosOverlayList(build_path, target):
287 """Use the cros_overlay_list to determine the path to the board overlay
288 Args:
289 build_path: The path to the root of the build directory
290 target: The target that we are looking for, could consist of board and
291 board_variant, we handle that properly
292 Returns:
293 The last line from cros_overlay_list as a string
294 """
Chris Sosa471532a2011-02-01 15:10:06 -0800295 script_dir = os.path.join(build_path, 'src/platform/dev/host')
David James8c846492011-01-25 17:07:29 -0800296 cmd = ['./cros_overlay_list']
297 if re.match('.*?_.*', target):
298 (board, variant) = target.split('_')
299 cmd += ['--board', board, '--variant', variant]
300 elif re.match('.*?-\w+', target):
301 cmd += ['--board', target]
Scott Zawalski1c660ea2011-06-24 19:12:58 -0700302 elif target == 'stumpy':
303 cmd += ['--board', target]
David James8c846492011-01-25 17:07:29 -0800304 else:
305 raise UnknownBoardFormat('Unknown format: %s' % target)
306
307 cmd_output = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
308 cwd=script_dir)
309 # We only care about the last entry
310 return cmd_output.output.splitlines().pop()
311
312
313def DeterminePrebuiltConfFile(build_path, target):
314 """Determine the prebuilt.conf file that needs to be updated for prebuilts.
315
316 Args:
317 build_path: The path to the root of the build directory
318 target: String representation of the board. This includes host and board
319 targets
320
321 Returns
322 A string path to a prebuilt.conf file to be updated.
323 """
324 if _HOST_TARGET == target:
325 # We are host.
326 # Without more examples of hosts this is a kludge for now.
327 # TODO(Scottz): as new host targets come online expand this to
328 # work more like boards.
Chris Sosa471532a2011-02-01 15:10:06 -0800329 make_path = _PREBUILT_MAKE_CONF[target]
David James8c846492011-01-25 17:07:29 -0800330 else:
331 # We are a board
332 board = GetBoardPathFromCrosOverlayList(build_path, target)
333 make_path = os.path.join(board, 'prebuilt.conf')
334
335 return make_path
336
337
338def UpdateBinhostConfFile(path, key, value):
339 """Update binhost config file file with key=value.
340
341 Args:
342 path: Filename to update.
343 key: Key to update.
344 value: New value for key.
345 """
346 cwd = os.path.dirname(os.path.abspath(path))
347 filename = os.path.basename(path)
348 if not os.path.isdir(cwd):
349 os.makedirs(cwd)
350 if not os.path.isfile(path):
351 config_file = file(path, 'w')
David James8c846492011-01-25 17:07:29 -0800352 config_file.close()
353 UpdateLocalFile(path, value, key)
Chris Sosac13bba52011-05-24 15:14:09 -0700354 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800355 description = 'Update %s=%s in %s' % (key, value, filename)
Peter Mayo193f68f2011-04-19 19:08:21 -0400356 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800357
358
David Jamesce093af2011-02-23 15:21:58 -0800359def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800360 """Grab all of the packages files associated with a list of binhost_urls.
361
David James05bcb2b2011-02-09 09:25:47 -0800362 Args:
363 binhost_urls: The URLs for the directories containing the Packages files we
364 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800365
366 Returns:
367 A list of PackageIndex objects.
368 """
369 pkg_indexes = []
370 for url in binhost_urls:
371 pkg_index = GrabRemotePackageIndex(url)
372 if pkg_index:
373 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800374 return pkg_indexes
375
376
David James05bcb2b2011-02-09 09:25:47 -0800377
David Jamesc0f158a2011-02-22 16:07:29 -0800378class PrebuiltUploader(object):
379 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800380
David James615e5b52011-06-03 11:10:15 -0700381 def __init__(self, upload_location, acl, binhost_base_url,
David James32b0b2f2011-07-13 20:56:50 -0700382 pkg_indexes, build_path, packages, skip_upload,
383 binhost_conf_dir, debug):
David Jamesc0f158a2011-02-22 16:07:29 -0800384 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800385
David Jamesc0f158a2011-02-22 16:07:29 -0800386 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800387
David Jamesc0f158a2011-02-22 16:07:29 -0800388 Args:
389 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800390 acl: The canned acl used for uploading to Google Storage. acl can be one
391 of: "public-read", "public-read-write", "authenticated-read",
392 "bucket-owner-read", "bucket-owner-full-control", or "private". If
393 we are not uploading to Google Storage, this parameter is unused.
394 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800395 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
396 uploading duplicate files, we just link to the old files.
David James615e5b52011-06-03 11:10:15 -0700397 build_path: The path to the directory containing the chroot.
398 packages: Packages to upload.
David James32b0b2f2011-07-13 20:56:50 -0700399 skip_upload: Don't actually upload the tarballs.
400 binhost_conf_dir: Directory where to store binhost.conf files.
401 debug: Don't push or upload prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800402 """
403 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800404 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800405 self._binhost_base_url = binhost_base_url
406 self._pkg_indexes = pkg_indexes
David James615e5b52011-06-03 11:10:15 -0700407 self._build_path = build_path
408 self._packages = set(packages)
David James8ece7ee2011-06-29 16:02:30 -0700409 self._skip_upload = skip_upload
David James32b0b2f2011-07-13 20:56:50 -0700410 self._binhost_conf_dir = binhost_conf_dir
David James27fa7d12011-06-29 17:24:14 -0700411 self._debug = debug
David James615e5b52011-06-03 11:10:15 -0700412
413 def _ShouldFilterPackage(self, pkg):
414 if not self._packages:
415 return False
416 pym_path = os.path.abspath(os.path.join(self._build_path, _PYM_PATH))
417 sys.path.append(pym_path)
418 import portage.versions
419 cat, pkgname = portage.versions.catpkgsplit(pkg['CPV'])[0:2]
420 cp = '%s/%s' % (cat, pkgname)
421 return pkgname not in self._packages and cp not in self._packages
David James8c846492011-01-25 17:07:29 -0800422
David Jamesc0f158a2011-02-22 16:07:29 -0800423 def _UploadPrebuilt(self, package_path, url_suffix):
424 """Upload host or board prebuilt files to Google Storage space.
David James8c846492011-01-25 17:07:29 -0800425
David Jamesc0f158a2011-02-22 16:07:29 -0800426 Args:
427 package_path: The path to the packages dir.
David Jamesce093af2011-02-23 15:21:58 -0800428 url_suffix: The remote subdirectory where we should upload the packages.
David Jamesa3bba142011-05-26 21:24:20 -0700429
David Jamesc0f158a2011-02-22 16:07:29 -0800430 """
David James8c846492011-01-25 17:07:29 -0800431
David Jamesc0f158a2011-02-22 16:07:29 -0800432 # Process Packages file, removing duplicates and filtered packages.
433 pkg_index = GrabLocalPackageIndex(package_path)
434 pkg_index.SetUploadLocation(self._binhost_base_url, url_suffix)
David James615e5b52011-06-03 11:10:15 -0700435 pkg_index.RemoveFilteredPackages(self._ShouldFilterPackage)
David Jamesc0f158a2011-02-22 16:07:29 -0800436 uploads = pkg_index.ResolveDuplicateUploads(self._pkg_indexes)
David James05bcb2b2011-02-09 09:25:47 -0800437
David Jamesc0f158a2011-02-22 16:07:29 -0800438 # Write Packages file.
439 tmp_packages_file = pkg_index.WriteToNamedTemporaryFile()
David James05bcb2b2011-02-09 09:25:47 -0800440
David Jamesc0f158a2011-02-22 16:07:29 -0800441 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
442 if remote_location.startswith('gs://'):
443 # Build list of files to upload.
444 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
445 remote_file = '%s/Packages' % remote_location.rstrip('/')
446 upload_files[tmp_packages_file.name] = remote_file
David James05bcb2b2011-02-09 09:25:47 -0800447
David Jamesfd0b0852011-02-23 11:15:36 -0800448 failed_uploads = RemoteUpload(self._acl, upload_files)
David Jamesc0f158a2011-02-22 16:07:29 -0800449 if len(failed_uploads) > 1 or (None not in failed_uploads):
David Jamesf4db1122011-03-17 16:18:05 -0700450 error_msg = ['%s -> %s\n' % args for args in failed_uploads if args]
David Jamesc0f158a2011-02-22 16:07:29 -0800451 raise UploadFailed('Error uploading:\n%s' % error_msg)
452 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400453 pkgs = [p['CPV'] + '.tbz2' for p in uploads]
David Jamesc0f158a2011-02-22 16:07:29 -0800454 ssh_server, remote_path = remote_location.split(':', 1)
Peter Mayo193f68f2011-04-19 19:08:21 -0400455 remote_path = remote_path.rstrip('/')
456 pkg_index = tmp_packages_file.name
457 remote_location = remote_location.rstrip('/')
458 remote_packages = '%s/Packages' % remote_location
459 cmds = [['ssh', ssh_server, 'mkdir', '-p', remote_path],
460 ['rsync', '-av', '--chmod=a+r', pkg_index, remote_packages]]
David Jamesc0f158a2011-02-22 16:07:29 -0800461 if pkgs:
Peter Mayo193f68f2011-04-19 19:08:21 -0400462 cmds.append(['rsync', '-Rav'] + pkgs + [remote_location + '/'])
David Jamesc0f158a2011-02-22 16:07:29 -0800463 for cmd in cmds:
Peter Mayo193f68f2011-04-19 19:08:21 -0400464 if not _RetryRun(cmd, cwd=package_path):
465 raise UploadFailed('Could not run %r' % cmd)
David James8c846492011-01-25 17:07:29 -0800466
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200467 def _UploadBoardTarball(self, board_path, url_suffix, version):
David James8fa34ea2011-04-15 13:00:20 -0700468 """Upload a tarball of the board at the specified path to Google Storage.
469
470 Args:
471 board_path: The path to the board dir.
472 url_suffix: The remote subdirectory where we should upload the packages.
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200473 version: The version of the board.
David James8fa34ea2011-04-15 13:00:20 -0700474 """
475 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
476 assert remote_location.startswith('gs://')
477 cwd, boardname = os.path.split(board_path.rstrip(os.path.sep))
478 tmpdir = tempfile.mkdtemp()
479 try:
480 tarfile = os.path.join(tmpdir, '%s.tbz2' % boardname)
481 cmd = ['sudo', 'tar', '-I', 'pbzip2', '-cf', tarfile]
482 excluded_paths = ('usr/lib/debug', 'usr/local/autotest', 'packages',
483 'tmp')
484 for path in excluded_paths:
Zdenek Behane3ed3462011-06-16 00:36:08 +0200485 cmd.append('--exclude=%s/*' % path)
486 cmd.append('.')
487 cros_build_lib.RunCommand(cmd, cwd=os.path.join(cwd, boardname))
David James8fa34ea2011-04-15 13:00:20 -0700488 remote_tarfile = '%s/%s.tbz2' % (remote_location.rstrip('/'), boardname)
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200489 # FIXME(zbehan): Temporary hack to upload amd64-host chroots to a
490 # different gs bucket. The right way is to do the upload in a separate
491 # pass of this script.
492 if boardname == 'amd64-host':
Zdenek Behanaf3c9002011-06-24 10:07:40 +0200493 # FIXME(zbehan): Why does version contain the prefix "chroot-"?
494 remote_tarfile = \
495 'gs://chromiumos-sdk/cros-sdk-%s.tbz2' % version.strip('chroot-')
David James8fa34ea2011-04-15 13:00:20 -0700496 if _GsUpload((tarfile, remote_tarfile, self._acl)):
497 sys.exit(1)
498 finally:
499 cros_build_lib.RunCommand(['sudo', 'rm', '-rf', tmpdir], cwd=cwd)
500
David James615e5b52011-06-03 11:10:15 -0700501 def _SyncHostPrebuilts(self, version, key, git_sync, sync_binhost_conf):
David Jamesc0f158a2011-02-22 16:07:29 -0800502 """Synchronize host prebuilt files.
David James05bcb2b2011-02-09 09:25:47 -0800503
David Jamesc0f158a2011-02-22 16:07:29 -0800504 This function will sync both the standard host packages, plus the host
505 packages associated with all targets that have been "setup" with the
506 current host's chroot. For instance, if this host has been used to build
507 x86-generic, it will sync the host packages associated with
508 'i686-pc-linux-gnu'. If this host has also been used to build arm-generic,
509 it will also sync the host packages associated with
510 'armv7a-cros-linux-gnueabi'.
David James05bcb2b2011-02-09 09:25:47 -0800511
David Jamesc0f158a2011-02-22 16:07:29 -0800512 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800513 version: A unique string, intended to be included in the upload path,
514 which identifies the version number of the uploaded prebuilts.
515 key: The variable key to update in the git file.
516 git_sync: If set, update make.conf of target to reference the latest
517 prebuilt packages generated here.
518 sync_binhost_conf: If set, update binhost config file in
519 chromiumos-overlay for the host.
520 """
521 # Upload prebuilts.
David James615e5b52011-06-03 11:10:15 -0700522 package_path = os.path.join(self._build_path, _HOST_PACKAGES_PATH)
David Jamesc0f158a2011-02-22 16:07:29 -0800523 url_suffix = _REL_HOST_PATH % {'version': version, 'target': _HOST_TARGET}
David James8fa34ea2011-04-15 13:00:20 -0700524 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James05bcb2b2011-02-09 09:25:47 -0800525
David James27fa7d12011-06-29 17:24:14 -0700526 if not self._skip_upload and not self._debug:
David James8ece7ee2011-06-29 16:02:30 -0700527 self._UploadPrebuilt(package_path, packages_url_suffix)
528
529 # Record URL where prebuilts were uploaded.
530 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
531 packages_url_suffix.rstrip('/'))
532 if git_sync:
533 git_file = os.path.join(self._build_path,
534 _PREBUILT_MAKE_CONF[_HOST_TARGET])
David James27fa7d12011-06-29 17:24:14 -0700535 RevGitFile(git_file, url_value, key=key, dryrun=self._debug)
David James8ece7ee2011-06-29 16:02:30 -0700536 if sync_binhost_conf:
David James32b0b2f2011-07-13 20:56:50 -0700537 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James8ece7ee2011-06-29 16:02:30 -0700538 'host', '%s-%s.conf' % (_HOST_TARGET, key))
539 UpdateBinhostConfFile(binhost_conf, key, url_value)
David Jamesc0f158a2011-02-22 16:07:29 -0800540
David James615e5b52011-06-03 11:10:15 -0700541 def _SyncBoardPrebuilts(self, board, version, key, git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700542 sync_binhost_conf, upload_board_tarball):
David Jamesc0f158a2011-02-22 16:07:29 -0800543 """Synchronize board prebuilt files.
544
545 Args:
546 board: The board to upload to Google Storage.
David Jamesc0f158a2011-02-22 16:07:29 -0800547 version: A unique string, intended to be included in the upload path,
548 which identifies the version number of the uploaded prebuilts.
549 key: The variable key to update in the git file.
550 git_sync: If set, update make.conf of target to reference the latest
551 prebuilt packages generated here.
552 sync_binhost_conf: If set, update binhost config file in
553 chromiumos-overlay for the current board.
David James8fa34ea2011-04-15 13:00:20 -0700554 upload_board_tarball: Include a tarball of the board in our upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800555 """
David James615e5b52011-06-03 11:10:15 -0700556 board_path = os.path.join(self._build_path, _BOARD_PATH % {'board': board})
David Jamesc0f158a2011-02-22 16:07:29 -0800557 package_path = os.path.join(board_path, 'packages')
558 url_suffix = _REL_BOARD_PATH % {'board': board, 'version': version}
David James8fa34ea2011-04-15 13:00:20 -0700559 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
560
David James27fa7d12011-06-29 17:24:14 -0700561 if not self._skip_upload and not self._debug:
David James8ece7ee2011-06-29 16:02:30 -0700562 # Upload board tarballs in the background.
563 if upload_board_tarball:
564 tar_process = multiprocessing.Process(target=self._UploadBoardTarball,
565 args=(board_path, url_suffix,
566 version))
567 tar_process.start()
David James8fa34ea2011-04-15 13:00:20 -0700568
David James8ece7ee2011-06-29 16:02:30 -0700569 # Upload prebuilts.
570 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8fa34ea2011-04-15 13:00:20 -0700571
David James8ece7ee2011-06-29 16:02:30 -0700572 # Make sure we finished uploading the board tarballs.
573 if upload_board_tarball:
574 tar_process.join()
575 assert tar_process.exitcode == 0
576 # TODO(zbehan): This should be done cleaner.
577 if board == 'amd64-host':
David James32b0b2f2011-07-13 20:56:50 -0700578 sdk_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James8ece7ee2011-06-29 16:02:30 -0700579 'host/sdk_version.conf')
580 RevGitFile(sdk_conf, version.strip('chroot-'),
David James27fa7d12011-06-29 17:24:14 -0700581 key='SDK_LATEST_VERSION', dryrun=self._debug)
David Jamesc0f158a2011-02-22 16:07:29 -0800582
David James8ece7ee2011-06-29 16:02:30 -0700583 # Record URL where prebuilts were uploaded.
584 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
585 packages_url_suffix.rstrip('/'))
586 if git_sync:
587 git_file = DeterminePrebuiltConfFile(self._build_path, board)
David James27fa7d12011-06-29 17:24:14 -0700588 RevGitFile(git_file, url_value, key=key, dryrun=self._debug)
David James8ece7ee2011-06-29 16:02:30 -0700589 if sync_binhost_conf:
David James32b0b2f2011-07-13 20:56:50 -0700590 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James8ece7ee2011-06-29 16:02:30 -0700591 'target', '%s-%s.conf' % (board, key))
592 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800593
594
David James8c846492011-01-25 17:07:29 -0800595def usage(parser, msg):
596 """Display usage message and parser help then exit with 1."""
597 print >> sys.stderr, msg
598 parser.print_help()
599 sys.exit(1)
600
David Jamesc0f158a2011-02-22 16:07:29 -0800601def ParseOptions():
David James8c846492011-01-25 17:07:29 -0800602 parser = optparse.OptionParser()
603 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
604 default=_BINHOST_BASE_URL,
605 help='Base URL to use for binhost in make.conf updates')
606 parser.add_option('', '--previous-binhost-url', action='append',
607 default=[], dest='previous_binhost_url',
608 help='Previous binhost URL')
609 parser.add_option('-b', '--board', dest='board', default=None,
610 help='Board type that was built on this machine')
611 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800612 help='Path to the directory containing the chroot')
David James615e5b52011-06-03 11:10:15 -0700613 parser.add_option('', '--packages', action='append',
614 default=[], dest='packages',
615 help='Only include the specified packages. '
616 '(Default is to include all packages.)')
David James8c846492011-01-25 17:07:29 -0800617 parser.add_option('-s', '--sync-host', dest='sync_host',
618 default=False, action='store_true',
619 help='Sync host prebuilts')
620 parser.add_option('-g', '--git-sync', dest='git_sync',
621 default=False, action='store_true',
622 help='Enable git version sync (This commits to a repo)')
623 parser.add_option('-u', '--upload', dest='upload',
624 default=None,
625 help='Upload location')
626 parser.add_option('-V', '--prepend-version', dest='prepend_version',
627 default=None,
628 help='Add an identifier to the front of the version')
629 parser.add_option('-f', '--filters', dest='filters', action='store_true',
630 default=False,
631 help='Turn on filtering of private ebuild packages')
632 parser.add_option('-k', '--key', dest='key',
633 default='PORTAGE_BINHOST',
634 help='Key to update in make.conf / binhost.conf')
David James8ece7ee2011-06-29 16:02:30 -0700635 parser.add_option('', '--set-version', dest='set_version',
636 default=None,
637 help='Specify the version string')
David James8c846492011-01-25 17:07:29 -0800638 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
639 default=False, action='store_true',
640 help='Update binhost.conf')
David James32b0b2f2011-07-13 20:56:50 -0700641 parser.add_option('', '--binhost-conf-dir', dest='binhost_conf_dir',
642 default=_BINHOST_CONF_DIR,
643 help='Directory to commit binhost config with '
644 '--sync-binhost-conf.')
David Jamesfd0b0852011-02-23 11:15:36 -0800645 parser.add_option('-P', '--private', dest='private', action='store_true',
646 default=False, help='Mark gs:// uploads as private.')
David James8ece7ee2011-06-29 16:02:30 -0700647 parser.add_option('', '--skip-upload', dest='skip_upload',
648 action='store_true', default=False,
649 help='Skip upload step.')
David James8fa34ea2011-04-15 13:00:20 -0700650 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
651 action='store_true', default=False,
652 help='Upload board tarball to Google Storage.')
David James27fa7d12011-06-29 17:24:14 -0700653 parser.add_option('', '--debug', dest='debug',
654 action='store_true', default=False,
655 help='Don\'t push or upload prebuilts.')
David James8c846492011-01-25 17:07:29 -0800656
657 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800658 if not options.build_path:
659 usage(parser, 'Error: you need provide a chroot path')
David James8ece7ee2011-06-29 16:02:30 -0700660 if not options.upload and not options.skip_upload:
David James8c846492011-01-25 17:07:29 -0800661 usage(parser, 'Error: you need to provide an upload location using -u')
David James8ece7ee2011-06-29 16:02:30 -0700662 if not options.set_version and options.skip_upload:
663 usage(parser, 'Error: If you are using --skip-upload, you must specify a '
664 'version number using --set-version.')
David James9417f272011-05-26 13:24:47 -0700665 if args:
666 usage(parser, 'Error: invalid arguments passed to prebuilt.py: %r' % args)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700667
David James8ece7ee2011-06-29 16:02:30 -0700668 if (options.upload_board_tarball and options.skip_upload and
669 options.board == 'amd64-host'):
670 usage(parser, 'Error: --skip-upload is not compatible with '
671 '--upload-board-tarball and --board=amd64-host')
David James8fa34ea2011-04-15 13:00:20 -0700672
David James8ece7ee2011-06-29 16:02:30 -0700673 if (options.upload_board_tarball and not options.skip_upload and
674 not options.upload.startswith('gs://')):
David James8fa34ea2011-04-15 13:00:20 -0700675 usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
676 '--upload must be a gs:// URL.')
677
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700678 if options.private:
679 if options.sync_host:
680 usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
681 'together, we do not support private host prebuilts')
682
David James8ece7ee2011-06-29 16:02:30 -0700683 if not options.upload or not options.upload.startswith('gs://'):
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700684 usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
685 '--upload must be a gs:// URL.')
686
687 if options.binhost_base_url != _BINHOST_BASE_URL:
688 usage(parser, 'Error: when using --private the --binhost-base-url '
689 'is automatically derived.')
David James27fa7d12011-06-29 17:24:14 -0700690
David Jamesc0f158a2011-02-22 16:07:29 -0800691 return options
692
693def main():
David Jamesdb401072011-06-10 12:17:16 -0700694 # Set umask to a sane value so that files created as root are readable.
695 os.umask(022)
696
David Jamesc0f158a2011-02-22 16:07:29 -0800697 options = ParseOptions()
698
David James05bcb2b2011-02-09 09:25:47 -0800699 # Calculate a list of Packages index files to compare against. Whenever we
700 # upload a package, we check to make sure it's not already stored in one of
701 # the packages files we uploaded. This list of packages files might contain
702 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800703 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800704
David James8ece7ee2011-06-29 16:02:30 -0700705 if options.set_version:
706 version = options.set_version
707 else:
708 version = GetVersion()
David Jamesc0f158a2011-02-22 16:07:29 -0800709 if options.prepend_version:
710 version = '%s-%s' % (options.prepend_version, version)
711
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700712 acl = 'public-read'
713 binhost_base_url = options.binhost_base_url
714
715 if options.private:
716 binhost_base_url = options.upload
717 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
718 options.board)
719 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
720
721 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David James615e5b52011-06-03 11:10:15 -0700722 pkg_indexes, options.build_path,
David James27fa7d12011-06-29 17:24:14 -0700723 options.packages, options.skip_upload,
David James32b0b2f2011-07-13 20:56:50 -0700724 options.binhost_conf_dir, options.debug)
David Jamesc0f158a2011-02-22 16:07:29 -0800725
David James8c846492011-01-25 17:07:29 -0800726 if options.sync_host:
David James615e5b52011-06-03 11:10:15 -0700727 uploader._SyncHostPrebuilts(version, options.key, options.git_sync,
728 options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800729
730 if options.board:
David James615e5b52011-06-03 11:10:15 -0700731 uploader._SyncBoardPrebuilts(options.board, version,
David Jamesc0f158a2011-02-22 16:07:29 -0800732 options.key, options.git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700733 options.sync_binhost_conf,
734 options.upload_board_tarball)
David James8c846492011-01-25 17:07:29 -0800735
736if __name__ == '__main__':
737 main()