blob: 25a6b9c454cbee597e8525ec71690775fb8243ed [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)
David Jamesbcfdc5d2011-08-25 20:46:33 -0700143 _RetryRun(['git', 'remote', 'update'], cwd=cwd)
David James1b6e67a2011-05-19 21:32:38 -0700144 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]
David James8c846492011-01-25 17:07:29 -0800300 else:
Chris Sosa6c03c0f2011-07-29 09:23:06 -0700301 cmd += ['--board', target]
David James8c846492011-01-25 17:07:29 -0800302
303 cmd_output = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
304 cwd=script_dir)
305 # We only care about the last entry
306 return cmd_output.output.splitlines().pop()
307
308
309def DeterminePrebuiltConfFile(build_path, target):
310 """Determine the prebuilt.conf file that needs to be updated for prebuilts.
311
312 Args:
313 build_path: The path to the root of the build directory
314 target: String representation of the board. This includes host and board
315 targets
316
317 Returns
318 A string path to a prebuilt.conf file to be updated.
319 """
320 if _HOST_TARGET == target:
321 # We are host.
322 # Without more examples of hosts this is a kludge for now.
323 # TODO(Scottz): as new host targets come online expand this to
324 # work more like boards.
Chris Sosa471532a2011-02-01 15:10:06 -0800325 make_path = _PREBUILT_MAKE_CONF[target]
David James8c846492011-01-25 17:07:29 -0800326 else:
327 # We are a board
328 board = GetBoardPathFromCrosOverlayList(build_path, target)
329 make_path = os.path.join(board, 'prebuilt.conf')
330
331 return make_path
332
333
334def UpdateBinhostConfFile(path, key, value):
335 """Update binhost config file file with key=value.
336
337 Args:
338 path: Filename to update.
339 key: Key to update.
340 value: New value for key.
341 """
342 cwd = os.path.dirname(os.path.abspath(path))
343 filename = os.path.basename(path)
344 if not os.path.isdir(cwd):
345 os.makedirs(cwd)
346 if not os.path.isfile(path):
347 config_file = file(path, 'w')
David James8c846492011-01-25 17:07:29 -0800348 config_file.close()
349 UpdateLocalFile(path, value, key)
Chris Sosac13bba52011-05-24 15:14:09 -0700350 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800351 description = 'Update %s=%s in %s' % (key, value, filename)
Peter Mayo193f68f2011-04-19 19:08:21 -0400352 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800353
354
David Jamesce093af2011-02-23 15:21:58 -0800355def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800356 """Grab all of the packages files associated with a list of binhost_urls.
357
David James05bcb2b2011-02-09 09:25:47 -0800358 Args:
359 binhost_urls: The URLs for the directories containing the Packages files we
360 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800361
362 Returns:
363 A list of PackageIndex objects.
364 """
365 pkg_indexes = []
366 for url in binhost_urls:
367 pkg_index = GrabRemotePackageIndex(url)
368 if pkg_index:
369 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800370 return pkg_indexes
371
372
David James05bcb2b2011-02-09 09:25:47 -0800373
David Jamesc0f158a2011-02-22 16:07:29 -0800374class PrebuiltUploader(object):
375 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800376
David James615e5b52011-06-03 11:10:15 -0700377 def __init__(self, upload_location, acl, binhost_base_url,
David James32b0b2f2011-07-13 20:56:50 -0700378 pkg_indexes, build_path, packages, skip_upload,
379 binhost_conf_dir, debug):
David Jamesc0f158a2011-02-22 16:07:29 -0800380 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800381
David Jamesc0f158a2011-02-22 16:07:29 -0800382 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800383
David Jamesc0f158a2011-02-22 16:07:29 -0800384 Args:
385 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800386 acl: The canned acl used for uploading to Google Storage. acl can be one
387 of: "public-read", "public-read-write", "authenticated-read",
388 "bucket-owner-read", "bucket-owner-full-control", or "private". If
389 we are not uploading to Google Storage, this parameter is unused.
390 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800391 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
392 uploading duplicate files, we just link to the old files.
David James615e5b52011-06-03 11:10:15 -0700393 build_path: The path to the directory containing the chroot.
394 packages: Packages to upload.
David James32b0b2f2011-07-13 20:56:50 -0700395 skip_upload: Don't actually upload the tarballs.
396 binhost_conf_dir: Directory where to store binhost.conf files.
397 debug: Don't push or upload prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800398 """
399 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800400 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800401 self._binhost_base_url = binhost_base_url
402 self._pkg_indexes = pkg_indexes
David James615e5b52011-06-03 11:10:15 -0700403 self._build_path = build_path
404 self._packages = set(packages)
David James8ece7ee2011-06-29 16:02:30 -0700405 self._skip_upload = skip_upload
David James32b0b2f2011-07-13 20:56:50 -0700406 self._binhost_conf_dir = binhost_conf_dir
David James27fa7d12011-06-29 17:24:14 -0700407 self._debug = debug
David James615e5b52011-06-03 11:10:15 -0700408
409 def _ShouldFilterPackage(self, pkg):
410 if not self._packages:
411 return False
412 pym_path = os.path.abspath(os.path.join(self._build_path, _PYM_PATH))
413 sys.path.append(pym_path)
414 import portage.versions
415 cat, pkgname = portage.versions.catpkgsplit(pkg['CPV'])[0:2]
416 cp = '%s/%s' % (cat, pkgname)
417 return pkgname not in self._packages and cp not in self._packages
David James8c846492011-01-25 17:07:29 -0800418
David Jamesc0f158a2011-02-22 16:07:29 -0800419 def _UploadPrebuilt(self, package_path, url_suffix):
420 """Upload host or board prebuilt files to Google Storage space.
David James8c846492011-01-25 17:07:29 -0800421
David Jamesc0f158a2011-02-22 16:07:29 -0800422 Args:
423 package_path: The path to the packages dir.
David Jamesce093af2011-02-23 15:21:58 -0800424 url_suffix: The remote subdirectory where we should upload the packages.
David Jamesa3bba142011-05-26 21:24:20 -0700425
David Jamesc0f158a2011-02-22 16:07:29 -0800426 """
David James8c846492011-01-25 17:07:29 -0800427
David Jamesc0f158a2011-02-22 16:07:29 -0800428 # Process Packages file, removing duplicates and filtered packages.
429 pkg_index = GrabLocalPackageIndex(package_path)
430 pkg_index.SetUploadLocation(self._binhost_base_url, url_suffix)
David James615e5b52011-06-03 11:10:15 -0700431 pkg_index.RemoveFilteredPackages(self._ShouldFilterPackage)
David Jamesc0f158a2011-02-22 16:07:29 -0800432 uploads = pkg_index.ResolveDuplicateUploads(self._pkg_indexes)
David James05bcb2b2011-02-09 09:25:47 -0800433
David Jamesc0f158a2011-02-22 16:07:29 -0800434 # Write Packages file.
435 tmp_packages_file = pkg_index.WriteToNamedTemporaryFile()
David James05bcb2b2011-02-09 09:25:47 -0800436
David Jamesc0f158a2011-02-22 16:07:29 -0800437 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
438 if remote_location.startswith('gs://'):
439 # Build list of files to upload.
440 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
441 remote_file = '%s/Packages' % remote_location.rstrip('/')
442 upload_files[tmp_packages_file.name] = remote_file
David James05bcb2b2011-02-09 09:25:47 -0800443
David Jamesfd0b0852011-02-23 11:15:36 -0800444 failed_uploads = RemoteUpload(self._acl, upload_files)
David Jamesc0f158a2011-02-22 16:07:29 -0800445 if len(failed_uploads) > 1 or (None not in failed_uploads):
David Jamesf4db1122011-03-17 16:18:05 -0700446 error_msg = ['%s -> %s\n' % args for args in failed_uploads if args]
David Jamesc0f158a2011-02-22 16:07:29 -0800447 raise UploadFailed('Error uploading:\n%s' % error_msg)
448 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400449 pkgs = [p['CPV'] + '.tbz2' for p in uploads]
David Jamesc0f158a2011-02-22 16:07:29 -0800450 ssh_server, remote_path = remote_location.split(':', 1)
Peter Mayo193f68f2011-04-19 19:08:21 -0400451 remote_path = remote_path.rstrip('/')
452 pkg_index = tmp_packages_file.name
453 remote_location = remote_location.rstrip('/')
454 remote_packages = '%s/Packages' % remote_location
455 cmds = [['ssh', ssh_server, 'mkdir', '-p', remote_path],
456 ['rsync', '-av', '--chmod=a+r', pkg_index, remote_packages]]
David Jamesc0f158a2011-02-22 16:07:29 -0800457 if pkgs:
Peter Mayo193f68f2011-04-19 19:08:21 -0400458 cmds.append(['rsync', '-Rav'] + pkgs + [remote_location + '/'])
David Jamesc0f158a2011-02-22 16:07:29 -0800459 for cmd in cmds:
Peter Mayo193f68f2011-04-19 19:08:21 -0400460 if not _RetryRun(cmd, cwd=package_path):
461 raise UploadFailed('Could not run %r' % cmd)
David James8c846492011-01-25 17:07:29 -0800462
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200463 def _UploadBoardTarball(self, board_path, url_suffix, version):
David James8fa34ea2011-04-15 13:00:20 -0700464 """Upload a tarball of the board at the specified path to Google Storage.
465
466 Args:
467 board_path: The path to the board dir.
468 url_suffix: The remote subdirectory where we should upload the packages.
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200469 version: The version of the board.
David James8fa34ea2011-04-15 13:00:20 -0700470 """
471 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
472 assert remote_location.startswith('gs://')
473 cwd, boardname = os.path.split(board_path.rstrip(os.path.sep))
474 tmpdir = tempfile.mkdtemp()
475 try:
476 tarfile = os.path.join(tmpdir, '%s.tbz2' % boardname)
477 cmd = ['sudo', 'tar', '-I', 'pbzip2', '-cf', tarfile]
478 excluded_paths = ('usr/lib/debug', 'usr/local/autotest', 'packages',
479 'tmp')
480 for path in excluded_paths:
Zdenek Behane3ed3462011-06-16 00:36:08 +0200481 cmd.append('--exclude=%s/*' % path)
482 cmd.append('.')
483 cros_build_lib.RunCommand(cmd, cwd=os.path.join(cwd, boardname))
David James8fa34ea2011-04-15 13:00:20 -0700484 remote_tarfile = '%s/%s.tbz2' % (remote_location.rstrip('/'), boardname)
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200485 # FIXME(zbehan): Temporary hack to upload amd64-host chroots to a
486 # different gs bucket. The right way is to do the upload in a separate
487 # pass of this script.
488 if boardname == 'amd64-host':
Zdenek Behanaf3c9002011-06-24 10:07:40 +0200489 # FIXME(zbehan): Why does version contain the prefix "chroot-"?
490 remote_tarfile = \
491 'gs://chromiumos-sdk/cros-sdk-%s.tbz2' % version.strip('chroot-')
David James8fa34ea2011-04-15 13:00:20 -0700492 if _GsUpload((tarfile, remote_tarfile, self._acl)):
493 sys.exit(1)
494 finally:
495 cros_build_lib.RunCommand(['sudo', 'rm', '-rf', tmpdir], cwd=cwd)
496
David James615e5b52011-06-03 11:10:15 -0700497 def _SyncHostPrebuilts(self, version, key, git_sync, sync_binhost_conf):
David Jamesc0f158a2011-02-22 16:07:29 -0800498 """Synchronize host prebuilt files.
David James05bcb2b2011-02-09 09:25:47 -0800499
David Jamesc0f158a2011-02-22 16:07:29 -0800500 This function will sync both the standard host packages, plus the host
501 packages associated with all targets that have been "setup" with the
502 current host's chroot. For instance, if this host has been used to build
503 x86-generic, it will sync the host packages associated with
504 'i686-pc-linux-gnu'. If this host has also been used to build arm-generic,
505 it will also sync the host packages associated with
506 'armv7a-cros-linux-gnueabi'.
David James05bcb2b2011-02-09 09:25:47 -0800507
David Jamesc0f158a2011-02-22 16:07:29 -0800508 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800509 version: A unique string, intended to be included in the upload path,
510 which identifies the version number of the uploaded prebuilts.
511 key: The variable key to update in the git file.
512 git_sync: If set, update make.conf of target to reference the latest
513 prebuilt packages generated here.
514 sync_binhost_conf: If set, update binhost config file in
515 chromiumos-overlay for the host.
516 """
517 # Upload prebuilts.
David James615e5b52011-06-03 11:10:15 -0700518 package_path = os.path.join(self._build_path, _HOST_PACKAGES_PATH)
David Jamesc0f158a2011-02-22 16:07:29 -0800519 url_suffix = _REL_HOST_PATH % {'version': version, 'target': _HOST_TARGET}
David James8fa34ea2011-04-15 13:00:20 -0700520 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James05bcb2b2011-02-09 09:25:47 -0800521
David James27fa7d12011-06-29 17:24:14 -0700522 if not self._skip_upload and not self._debug:
David James8ece7ee2011-06-29 16:02:30 -0700523 self._UploadPrebuilt(package_path, packages_url_suffix)
524
525 # Record URL where prebuilts were uploaded.
526 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
527 packages_url_suffix.rstrip('/'))
528 if git_sync:
529 git_file = os.path.join(self._build_path,
530 _PREBUILT_MAKE_CONF[_HOST_TARGET])
David James27fa7d12011-06-29 17:24:14 -0700531 RevGitFile(git_file, url_value, key=key, dryrun=self._debug)
David James8ece7ee2011-06-29 16:02:30 -0700532 if sync_binhost_conf:
David James32b0b2f2011-07-13 20:56:50 -0700533 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James8ece7ee2011-06-29 16:02:30 -0700534 'host', '%s-%s.conf' % (_HOST_TARGET, key))
535 UpdateBinhostConfFile(binhost_conf, key, url_value)
David Jamesc0f158a2011-02-22 16:07:29 -0800536
David James615e5b52011-06-03 11:10:15 -0700537 def _SyncBoardPrebuilts(self, board, version, key, git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700538 sync_binhost_conf, upload_board_tarball):
David Jamesc0f158a2011-02-22 16:07:29 -0800539 """Synchronize board prebuilt files.
540
541 Args:
542 board: The board to upload to Google Storage.
David Jamesc0f158a2011-02-22 16:07:29 -0800543 version: A unique string, intended to be included in the upload path,
544 which identifies the version number of the uploaded prebuilts.
545 key: The variable key to update in the git file.
546 git_sync: If set, update make.conf of target to reference the latest
547 prebuilt packages generated here.
548 sync_binhost_conf: If set, update binhost config file in
549 chromiumos-overlay for the current board.
David James8fa34ea2011-04-15 13:00:20 -0700550 upload_board_tarball: Include a tarball of the board in our upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800551 """
David James615e5b52011-06-03 11:10:15 -0700552 board_path = os.path.join(self._build_path, _BOARD_PATH % {'board': board})
David Jamesc0f158a2011-02-22 16:07:29 -0800553 package_path = os.path.join(board_path, 'packages')
554 url_suffix = _REL_BOARD_PATH % {'board': board, 'version': version}
David James8fa34ea2011-04-15 13:00:20 -0700555 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
556
David James27fa7d12011-06-29 17:24:14 -0700557 if not self._skip_upload and not self._debug:
David James8ece7ee2011-06-29 16:02:30 -0700558 # Upload board tarballs in the background.
559 if upload_board_tarball:
560 tar_process = multiprocessing.Process(target=self._UploadBoardTarball,
561 args=(board_path, url_suffix,
562 version))
563 tar_process.start()
David James8fa34ea2011-04-15 13:00:20 -0700564
David James8ece7ee2011-06-29 16:02:30 -0700565 # Upload prebuilts.
566 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8fa34ea2011-04-15 13:00:20 -0700567
David James8ece7ee2011-06-29 16:02:30 -0700568 # Make sure we finished uploading the board tarballs.
569 if upload_board_tarball:
570 tar_process.join()
571 assert tar_process.exitcode == 0
572 # TODO(zbehan): This should be done cleaner.
573 if board == 'amd64-host':
David James32b0b2f2011-07-13 20:56:50 -0700574 sdk_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James8ece7ee2011-06-29 16:02:30 -0700575 'host/sdk_version.conf')
576 RevGitFile(sdk_conf, version.strip('chroot-'),
David James27fa7d12011-06-29 17:24:14 -0700577 key='SDK_LATEST_VERSION', dryrun=self._debug)
David Jamesc0f158a2011-02-22 16:07:29 -0800578
David James8ece7ee2011-06-29 16:02:30 -0700579 # Record URL where prebuilts were uploaded.
580 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
581 packages_url_suffix.rstrip('/'))
582 if git_sync:
583 git_file = DeterminePrebuiltConfFile(self._build_path, board)
David James27fa7d12011-06-29 17:24:14 -0700584 RevGitFile(git_file, url_value, key=key, dryrun=self._debug)
David James8ece7ee2011-06-29 16:02:30 -0700585 if sync_binhost_conf:
David James32b0b2f2011-07-13 20:56:50 -0700586 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James8ece7ee2011-06-29 16:02:30 -0700587 'target', '%s-%s.conf' % (board, key))
588 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800589
590
David James8c846492011-01-25 17:07:29 -0800591def usage(parser, msg):
592 """Display usage message and parser help then exit with 1."""
593 print >> sys.stderr, msg
594 parser.print_help()
595 sys.exit(1)
596
David Jamesc0f158a2011-02-22 16:07:29 -0800597def ParseOptions():
David James8c846492011-01-25 17:07:29 -0800598 parser = optparse.OptionParser()
599 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
600 default=_BINHOST_BASE_URL,
601 help='Base URL to use for binhost in make.conf updates')
602 parser.add_option('', '--previous-binhost-url', action='append',
603 default=[], dest='previous_binhost_url',
604 help='Previous binhost URL')
605 parser.add_option('-b', '--board', dest='board', default=None,
606 help='Board type that was built on this machine')
607 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800608 help='Path to the directory containing the chroot')
David James615e5b52011-06-03 11:10:15 -0700609 parser.add_option('', '--packages', action='append',
610 default=[], dest='packages',
611 help='Only include the specified packages. '
612 '(Default is to include all packages.)')
David James8c846492011-01-25 17:07:29 -0800613 parser.add_option('-s', '--sync-host', dest='sync_host',
614 default=False, action='store_true',
615 help='Sync host prebuilts')
616 parser.add_option('-g', '--git-sync', dest='git_sync',
617 default=False, action='store_true',
618 help='Enable git version sync (This commits to a repo)')
619 parser.add_option('-u', '--upload', dest='upload',
620 default=None,
621 help='Upload location')
622 parser.add_option('-V', '--prepend-version', dest='prepend_version',
623 default=None,
624 help='Add an identifier to the front of the version')
625 parser.add_option('-f', '--filters', dest='filters', action='store_true',
626 default=False,
627 help='Turn on filtering of private ebuild packages')
628 parser.add_option('-k', '--key', dest='key',
629 default='PORTAGE_BINHOST',
630 help='Key to update in make.conf / binhost.conf')
David James8ece7ee2011-06-29 16:02:30 -0700631 parser.add_option('', '--set-version', dest='set_version',
632 default=None,
633 help='Specify the version string')
David James8c846492011-01-25 17:07:29 -0800634 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
635 default=False, action='store_true',
636 help='Update binhost.conf')
David James32b0b2f2011-07-13 20:56:50 -0700637 parser.add_option('', '--binhost-conf-dir', dest='binhost_conf_dir',
638 default=_BINHOST_CONF_DIR,
639 help='Directory to commit binhost config with '
640 '--sync-binhost-conf.')
David Jamesfd0b0852011-02-23 11:15:36 -0800641 parser.add_option('-P', '--private', dest='private', action='store_true',
642 default=False, help='Mark gs:// uploads as private.')
David James8ece7ee2011-06-29 16:02:30 -0700643 parser.add_option('', '--skip-upload', dest='skip_upload',
644 action='store_true', default=False,
645 help='Skip upload step.')
David James8fa34ea2011-04-15 13:00:20 -0700646 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
647 action='store_true', default=False,
648 help='Upload board tarball to Google Storage.')
David James27fa7d12011-06-29 17:24:14 -0700649 parser.add_option('', '--debug', dest='debug',
650 action='store_true', default=False,
651 help='Don\'t push or upload prebuilts.')
David James8c846492011-01-25 17:07:29 -0800652
653 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800654 if not options.build_path:
655 usage(parser, 'Error: you need provide a chroot path')
David James8ece7ee2011-06-29 16:02:30 -0700656 if not options.upload and not options.skip_upload:
David James8c846492011-01-25 17:07:29 -0800657 usage(parser, 'Error: you need to provide an upload location using -u')
David James8ece7ee2011-06-29 16:02:30 -0700658 if not options.set_version and options.skip_upload:
659 usage(parser, 'Error: If you are using --skip-upload, you must specify a '
660 'version number using --set-version.')
David James9417f272011-05-26 13:24:47 -0700661 if args:
662 usage(parser, 'Error: invalid arguments passed to prebuilt.py: %r' % args)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700663
David James8ece7ee2011-06-29 16:02:30 -0700664 if (options.upload_board_tarball and options.skip_upload and
665 options.board == 'amd64-host'):
666 usage(parser, 'Error: --skip-upload is not compatible with '
667 '--upload-board-tarball and --board=amd64-host')
David James8fa34ea2011-04-15 13:00:20 -0700668
David James8ece7ee2011-06-29 16:02:30 -0700669 if (options.upload_board_tarball and not options.skip_upload and
670 not options.upload.startswith('gs://')):
David James8fa34ea2011-04-15 13:00:20 -0700671 usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
672 '--upload must be a gs:// URL.')
673
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700674 if options.private:
675 if options.sync_host:
676 usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
677 'together, we do not support private host prebuilts')
678
David James8ece7ee2011-06-29 16:02:30 -0700679 if not options.upload or not options.upload.startswith('gs://'):
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700680 usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
681 '--upload must be a gs:// URL.')
682
683 if options.binhost_base_url != _BINHOST_BASE_URL:
684 usage(parser, 'Error: when using --private the --binhost-base-url '
685 'is automatically derived.')
David James27fa7d12011-06-29 17:24:14 -0700686
David Jamesc0f158a2011-02-22 16:07:29 -0800687 return options
688
689def main():
David Jamesdb401072011-06-10 12:17:16 -0700690 # Set umask to a sane value so that files created as root are readable.
691 os.umask(022)
692
David Jamesc0f158a2011-02-22 16:07:29 -0800693 options = ParseOptions()
694
David James05bcb2b2011-02-09 09:25:47 -0800695 # Calculate a list of Packages index files to compare against. Whenever we
696 # upload a package, we check to make sure it's not already stored in one of
697 # the packages files we uploaded. This list of packages files might contain
698 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800699 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800700
David James8ece7ee2011-06-29 16:02:30 -0700701 if options.set_version:
702 version = options.set_version
703 else:
704 version = GetVersion()
David Jamesc0f158a2011-02-22 16:07:29 -0800705 if options.prepend_version:
706 version = '%s-%s' % (options.prepend_version, version)
707
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700708 acl = 'public-read'
709 binhost_base_url = options.binhost_base_url
710
711 if options.private:
712 binhost_base_url = options.upload
713 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
714 options.board)
715 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
716
717 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David James615e5b52011-06-03 11:10:15 -0700718 pkg_indexes, options.build_path,
David James27fa7d12011-06-29 17:24:14 -0700719 options.packages, options.skip_upload,
David James32b0b2f2011-07-13 20:56:50 -0700720 options.binhost_conf_dir, options.debug)
David Jamesc0f158a2011-02-22 16:07:29 -0800721
David James8c846492011-01-25 17:07:29 -0800722 if options.sync_host:
David James615e5b52011-06-03 11:10:15 -0700723 uploader._SyncHostPrebuilts(version, options.key, options.git_sync,
724 options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800725
726 if options.board:
David James615e5b52011-06-03 11:10:15 -0700727 uploader._SyncBoardPrebuilts(options.board, version,
David Jamesc0f158a2011-02-22 16:07:29 -0800728 options.key, options.git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700729 options.sync_binhost_conf,
730 options.upload_board_tarball)
David James8c846492011-01-25 17:07:29 -0800731
732if __name__ == '__main__':
733 main()