blob: 9a1ff110f18c5eedf3066953b820402e23fbee59 [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
13import time
14
Chris Sosa471532a2011-02-01 15:10:06 -080015if __name__ == '__main__':
16 import constants
17 sys.path.append(constants.SOURCE_ROOT)
18
David James8c846492011-01-25 17:07:29 -080019from chromite.lib import cros_build_lib
Chris Sosac13bba52011-05-24 15:14:09 -070020from chromite.lib.binpkg import (GrabLocalPackageIndex, GrabRemotePackageIndex)
David James8c846492011-01-25 17:07:29 -080021"""
22This script is used to upload host prebuilts as well as board BINHOSTS.
23
24If the URL starts with 'gs://', we upload using gsutil to Google Storage.
25Otherwise, rsync is used.
26
27After a build is successfully uploaded a file is updated with the proper
28BINHOST version as well as the target board. This file is defined in GIT_FILE
29
30
31To read more about prebuilts/binhost binary packages please refer to:
32http://sites/chromeos/for-team-members/engineering/releng/prebuilt-binaries-for-streamlining-the-build-process
33
34
35Example of uploading prebuilt amd64 host files to Google Storage:
36./prebuilt.py -p /b/cbuild/build -s -u gs://chromeos-prebuilt
37
38Example of uploading x86-dogfood binhosts to Google Storage:
39./prebuilt.py -b x86-dogfood -p /b/cbuild/build/ -u gs://chromeos-prebuilt -g
40
41Example of uploading prebuilt amd64 host files using rsync:
42./prebuilt.py -p /b/cbuild/build -s -u codf30.jail:/tmp
43"""
44
David James8c846492011-01-25 17:07:29 -080045_RETRIES = 3
46_GSUTIL_BIN = '/b/build/third_party/gsutil/gsutil'
47_HOST_PACKAGES_PATH = 'chroot/var/lib/portage/pkgs'
David James05bcb2b2011-02-09 09:25:47 -080048_CATEGORIES_PATH = 'chroot/etc/portage/categories'
David James615e5b52011-06-03 11:10:15 -070049_PYM_PATH = 'chroot/usr/lib/portage/pym'
David James8c846492011-01-25 17:07:29 -080050_HOST_TARGET = 'amd64'
51_BOARD_PATH = 'chroot/build/%(board)s'
David James8fa34ea2011-04-15 13:00:20 -070052# board/board-target/version/'
53_REL_BOARD_PATH = 'board/%(board)s/%(version)s'
54# host/host-target/version/'
55_REL_HOST_PATH = 'host/%(target)s/%(version)s'
David James8c846492011-01-25 17:07:29 -080056# Private overlays to look at for builds to filter
57# relative to build path
58_PRIVATE_OVERLAY_DIR = 'src/private-overlays'
Scott Zawalskiab1bed32011-03-16 15:24:24 -070059_GOOGLESTORAGE_ACL_FILE = 'googlestorage_acl.xml'
David James8c846492011-01-25 17:07:29 -080060_BINHOST_BASE_URL = 'http://commondatastorage.googleapis.com/chromeos-prebuilt'
61_PREBUILT_BASE_DIR = 'src/third_party/chromiumos-overlay/chromeos/config/'
62# Created in the event of new host targets becoming available
63_PREBUILT_MAKE_CONF = {'amd64': os.path.join(_PREBUILT_BASE_DIR,
64 'make.conf.amd64-host')}
65_BINHOST_CONF_DIR = 'src/third_party/chromiumos-overlay/chromeos/binhost'
66
67
David James8c846492011-01-25 17:07:29 -080068class UploadFailed(Exception):
69 """Raised when one of the files uploaded failed."""
70 pass
71
72class UnknownBoardFormat(Exception):
73 """Raised when a function finds an unknown board format."""
74 pass
75
David James8c846492011-01-25 17:07:29 -080076
77def UpdateLocalFile(filename, value, key='PORTAGE_BINHOST'):
78 """Update the key in file with the value passed.
79 File format:
80 key="value"
81 Note quotes are added automatically
82
83 Args:
84 filename: Name of file to modify.
85 value: Value to write with the key.
86 key: The variable key to update. (Default: PORTAGE_BINHOST)
87 """
88 if os.path.exists(filename):
89 file_fh = open(filename)
90 else:
91 file_fh = open(filename, 'w+')
92 file_lines = []
93 found = False
94 keyval_str = '%(key)s=%(value)s'
95 for line in file_fh:
96 # Strip newlines from end of line. We already add newlines below.
97 line = line.rstrip("\n")
98
99 if len(line.split('=')) != 2:
100 # Skip any line that doesn't fit key=val.
101 file_lines.append(line)
102 continue
103
104 file_var, file_val = line.split('=')
105 if file_var == key:
106 found = True
107 print 'Updating %s=%s to %s="%s"' % (file_var, file_val, key, value)
108 value = '"%s"' % value
109 file_lines.append(keyval_str % {'key': key, 'value': value})
110 else:
111 file_lines.append(keyval_str % {'key': file_var, 'value': file_val})
112
113 if not found:
114 file_lines.append(keyval_str % {'key': key, 'value': value})
115
116 file_fh.close()
117 # write out new file
118 new_file_fh = open(filename, 'w')
119 new_file_fh.write('\n'.join(file_lines) + '\n')
120 new_file_fh.close()
121
122
David James8c846492011-01-25 17:07:29 -0800123def RevGitFile(filename, value, retries=5, key='PORTAGE_BINHOST'):
124 """Update and push the git file.
125
126 Args:
127 filename: file to modify that is in a git repo already
128 value: string representing the version of the prebuilt that has been
129 uploaded.
130 retries: The number of times to retry before giving up, default: 5
131 key: The variable key to update in the git file.
132 (Default: PORTAGE_BINHOST)
133 """
134 prebuilt_branch = 'prebuilt_branch'
David James1b6e67a2011-05-19 21:32:38 -0700135 cwd = os.path.abspath(os.path.dirname(filename))
136 commit = cros_build_lib.RunCommand(['git', 'rev-parse', 'HEAD'], cwd=cwd,
Peter Mayofe0e6872011-04-20 00:52:08 -0400137 redirect_stdout=True).output.rstrip()
Peter Mayo193f68f2011-04-19 19:08:21 -0400138 git_ssh_config_cmd = [
139 'git',
140 'config',
Chris Sosac13bba52011-05-24 15:14:09 -0700141 'url.ssh://gerrit.chromium.org:29418.pushinsteadof',
David James1b6e67a2011-05-19 21:32:38 -0700142 'http://git.chromium.org']
143 cros_build_lib.RunCommand(git_ssh_config_cmd, cwd=cwd)
144 cros_build_lib.RunCommand(['git', 'remote', 'update'], cwd=cwd)
145 cros_build_lib.RunCommand(['repo', 'start', prebuilt_branch, '.'], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800146 description = 'Update %s="%s" in %s' % (key, value, filename)
147 print description
148 try:
149 UpdateLocalFile(filename, value, key)
David James1b6e67a2011-05-19 21:32:38 -0700150 cros_build_lib.RunCommand(['git', 'config', 'push.default', 'tracking'],
151 cwd=cwd)
152 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
153 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
Chris Sosac13bba52011-05-24 15:14:09 -0700154 cros_build_lib.GitPushWithRetry(prebuilt_branch, cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800155 finally:
David James1b6e67a2011-05-19 21:32:38 -0700156 cros_build_lib.RunCommand(['git', 'checkout', commit], cwd=cwd)
157 cros_build_lib.RunCommand(['repo', 'abandon', 'prebuilt_branch', '.'],
158 cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800159
160
161def GetVersion():
162 """Get the version to put in LATEST and update the git version with."""
163 return datetime.datetime.now().strftime('%d.%m.%y.%H%M%S')
164
165
Peter Mayo193f68f2011-04-19 19:08:21 -0400166def _RetryRun(cmd, print_cmd=True, cwd=None):
David James8c846492011-01-25 17:07:29 -0800167 """Run the specified command, retrying if necessary.
168
169 Args:
170 cmd: The command to run.
171 print_cmd: Whether to print out the cmd.
172 shell: Whether to treat the command as a shell.
173 cwd: Working directory to run command in.
174
175 Returns:
176 True if the command succeeded. Otherwise, returns False.
177 """
178
179 # TODO(scottz): port to use _Run or similar when it is available in
180 # cros_build_lib.
181 for attempt in range(_RETRIES):
182 try:
Peter Mayo193f68f2011-04-19 19:08:21 -0400183 output = cros_build_lib.RunCommand(cmd, print_cmd=print_cmd,
David James8c846492011-01-25 17:07:29 -0800184 cwd=cwd)
185 return True
186 except cros_build_lib.RunCommandError:
Peter Mayo193f68f2011-04-19 19:08:21 -0400187 print 'Failed to run %r' % cmd
David James8c846492011-01-25 17:07:29 -0800188 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400189 print 'Retry failed run %r, giving up' % cmd
David James8c846492011-01-25 17:07:29 -0800190 return False
191
192
193def _GsUpload(args):
194 """Upload to GS bucket.
195
196 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800197 args: a tuple of three arguments that contains local_file, remote_file, and
198 the acl used for uploading the file.
David James8c846492011-01-25 17:07:29 -0800199
200 Returns:
201 Return the arg tuple of two if the upload failed
202 """
David Jamesfd0b0852011-02-23 11:15:36 -0800203 (local_file, remote_file, acl) = args
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700204 CANNED_ACLS = ['public-read', 'private', 'bucket-owner-read',
205 'authenticated-read', 'bucket-owner-full-control',
206 'public-read-write']
207 acl_cmd = None
208 if acl in CANNED_ACLS:
Peter Mayo193f68f2011-04-19 19:08:21 -0400209 cmd = [_GSUTIL_BIN, 'cp', '-a', acl, local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700210 else:
211 # For private uploads we assume that the overlay board is set up properly
212 # and a googlestore_acl.xml is present, if not this script errors
Peter Mayo193f68f2011-04-19 19:08:21 -0400213 cmd = [_GSUTIL_BIN, 'cp', '-a', 'private', local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700214 if not os.path.exists(acl):
215 print >> sys.stderr, ('You are specifying either a file that does not '
216 'exist or an unknown canned acl: %s. Aborting '
217 'upload') % acl
218 # emulate the failing of an upload since we are not uploading the file
219 return (local_file, remote_file)
David James8c846492011-01-25 17:07:29 -0800220
Peter Mayo193f68f2011-04-19 19:08:21 -0400221 acl_cmd = [_GSUTIL_BIN, 'setacl', acl, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700222
Peter Mayo193f68f2011-04-19 19:08:21 -0400223 if not _RetryRun(cmd, print_cmd=False):
David James8c846492011-01-25 17:07:29 -0800224 return (local_file, remote_file)
225
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700226 if acl_cmd:
227 # Apply the passed in ACL xml file to the uploaded object.
Peter Mayo193f68f2011-04-19 19:08:21 -0400228 _RetryRun(acl_cmd, print_cmd=False)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700229
230
David Jamesfd0b0852011-02-23 11:15:36 -0800231def RemoteUpload(acl, files, pool=10):
David James8c846492011-01-25 17:07:29 -0800232 """Upload to google storage.
233
234 Create a pool of process and call _GsUpload with the proper arguments.
235
236 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800237 acl: The canned acl used for uploading. acl can be one of: "public-read",
238 "public-read-write", "authenticated-read", "bucket-owner-read",
239 "bucket-owner-full-control", or "private".
David James8c846492011-01-25 17:07:29 -0800240 files: dictionary with keys to local files and values to remote path.
241 pool: integer of maximum proesses to have at the same time.
242
243 Returns:
244 Return a set of tuple arguments of the failed uploads
245 """
246 # TODO(scottz) port this to use _RunManyParallel when it is available in
247 # cros_build_lib
248 pool = multiprocessing.Pool(processes=pool)
249 workers = []
250 for local_file, remote_path in files.iteritems():
David Jamesfd0b0852011-02-23 11:15:36 -0800251 workers.append((local_file, remote_path, acl))
David James8c846492011-01-25 17:07:29 -0800252
253 result = pool.map_async(_GsUpload, workers, chunksize=1)
254 while True:
255 try:
Chris Sosa471532a2011-02-01 15:10:06 -0800256 return set(result.get(60 * 60))
David James8c846492011-01-25 17:07:29 -0800257 except multiprocessing.TimeoutError:
258 pass
259
260
261def GenerateUploadDict(base_local_path, base_remote_path, pkgs):
262 """Build a dictionary of local remote file key pairs to upload.
263
264 Args:
265 base_local_path: The base path to the files on the local hard drive.
266 remote_path: The base path to the remote paths.
267 pkgs: The packages to upload.
268
269 Returns:
270 Returns a dictionary of local_path/remote_path pairs
271 """
272 upload_files = {}
273 for pkg in pkgs:
274 suffix = pkg['CPV'] + '.tbz2'
275 local_path = os.path.join(base_local_path, suffix)
276 assert os.path.exists(local_path)
277 remote_path = '%s/%s' % (base_remote_path.rstrip('/'), suffix)
278 upload_files[local_path] = remote_path
279
280 return upload_files
281
282def GetBoardPathFromCrosOverlayList(build_path, target):
283 """Use the cros_overlay_list to determine the path to the board overlay
284 Args:
285 build_path: The path to the root of the build directory
286 target: The target that we are looking for, could consist of board and
287 board_variant, we handle that properly
288 Returns:
289 The last line from cros_overlay_list as a string
290 """
Chris Sosa471532a2011-02-01 15:10:06 -0800291 script_dir = os.path.join(build_path, 'src/platform/dev/host')
David James8c846492011-01-25 17:07:29 -0800292 cmd = ['./cros_overlay_list']
293 if re.match('.*?_.*', target):
294 (board, variant) = target.split('_')
295 cmd += ['--board', board, '--variant', variant]
296 elif re.match('.*?-\w+', target):
297 cmd += ['--board', target]
298 else:
299 raise UnknownBoardFormat('Unknown format: %s' % target)
300
301 cmd_output = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
302 cwd=script_dir)
303 # We only care about the last entry
304 return cmd_output.output.splitlines().pop()
305
306
307def DeterminePrebuiltConfFile(build_path, target):
308 """Determine the prebuilt.conf file that needs to be updated for prebuilts.
309
310 Args:
311 build_path: The path to the root of the build directory
312 target: String representation of the board. This includes host and board
313 targets
314
315 Returns
316 A string path to a prebuilt.conf file to be updated.
317 """
318 if _HOST_TARGET == target:
319 # We are host.
320 # Without more examples of hosts this is a kludge for now.
321 # TODO(Scottz): as new host targets come online expand this to
322 # work more like boards.
Chris Sosa471532a2011-02-01 15:10:06 -0800323 make_path = _PREBUILT_MAKE_CONF[target]
David James8c846492011-01-25 17:07:29 -0800324 else:
325 # We are a board
326 board = GetBoardPathFromCrosOverlayList(build_path, target)
327 make_path = os.path.join(board, 'prebuilt.conf')
328
329 return make_path
330
331
332def UpdateBinhostConfFile(path, key, value):
333 """Update binhost config file file with key=value.
334
335 Args:
336 path: Filename to update.
337 key: Key to update.
338 value: New value for key.
339 """
340 cwd = os.path.dirname(os.path.abspath(path))
341 filename = os.path.basename(path)
342 if not os.path.isdir(cwd):
343 os.makedirs(cwd)
344 if not os.path.isfile(path):
345 config_file = file(path, 'w')
David James8c846492011-01-25 17:07:29 -0800346 config_file.close()
347 UpdateLocalFile(path, value, key)
Chris Sosac13bba52011-05-24 15:14:09 -0700348 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800349 description = 'Update %s=%s in %s' % (key, value, filename)
Peter Mayo193f68f2011-04-19 19:08:21 -0400350 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800351
352
David Jamesce093af2011-02-23 15:21:58 -0800353def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800354 """Grab all of the packages files associated with a list of binhost_urls.
355
David James05bcb2b2011-02-09 09:25:47 -0800356 Args:
357 binhost_urls: The URLs for the directories containing the Packages files we
358 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800359
360 Returns:
361 A list of PackageIndex objects.
362 """
363 pkg_indexes = []
364 for url in binhost_urls:
365 pkg_index = GrabRemotePackageIndex(url)
366 if pkg_index:
367 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800368 return pkg_indexes
369
370
David James05bcb2b2011-02-09 09:25:47 -0800371
David Jamesc0f158a2011-02-22 16:07:29 -0800372class PrebuiltUploader(object):
373 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800374
David James615e5b52011-06-03 11:10:15 -0700375 def __init__(self, upload_location, acl, binhost_base_url,
376 pkg_indexes, build_path, packages):
David Jamesc0f158a2011-02-22 16:07:29 -0800377 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800378
David Jamesc0f158a2011-02-22 16:07:29 -0800379 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800380
David Jamesc0f158a2011-02-22 16:07:29 -0800381 Args:
382 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800383 acl: The canned acl used for uploading to Google Storage. acl can be one
384 of: "public-read", "public-read-write", "authenticated-read",
385 "bucket-owner-read", "bucket-owner-full-control", or "private". If
386 we are not uploading to Google Storage, this parameter is unused.
387 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800388 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
389 uploading duplicate files, we just link to the old files.
David James615e5b52011-06-03 11:10:15 -0700390 build_path: The path to the directory containing the chroot.
391 packages: Packages to upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800392 """
393 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800394 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800395 self._binhost_base_url = binhost_base_url
396 self._pkg_indexes = pkg_indexes
David James615e5b52011-06-03 11:10:15 -0700397 self._build_path = build_path
398 self._packages = set(packages)
399
400 def _ShouldFilterPackage(self, pkg):
401 if not self._packages:
402 return False
403 pym_path = os.path.abspath(os.path.join(self._build_path, _PYM_PATH))
404 sys.path.append(pym_path)
405 import portage.versions
406 cat, pkgname = portage.versions.catpkgsplit(pkg['CPV'])[0:2]
407 cp = '%s/%s' % (cat, pkgname)
408 return pkgname not in self._packages and cp not in self._packages
David James8c846492011-01-25 17:07:29 -0800409
David Jamesc0f158a2011-02-22 16:07:29 -0800410 def _UploadPrebuilt(self, package_path, url_suffix):
411 """Upload host or board prebuilt files to Google Storage space.
David James8c846492011-01-25 17:07:29 -0800412
David Jamesa3bba142011-05-26 21:24:20 -0700413 This function checks to see if we have any new binaries, and, if so,
414 uploads them.
415
David Jamesc0f158a2011-02-22 16:07:29 -0800416 Args:
417 package_path: The path to the packages dir.
David Jamesce093af2011-02-23 15:21:58 -0800418 url_suffix: The remote subdirectory where we should upload the packages.
David Jamesa3bba142011-05-26 21:24:20 -0700419
420 Returns:
421 True if any prebuilts were uploaded.
422 False otherwise.
David Jamesc0f158a2011-02-22 16:07:29 -0800423 """
David James8c846492011-01-25 17:07:29 -0800424
David Jamesc0f158a2011-02-22 16:07:29 -0800425 # Process Packages file, removing duplicates and filtered packages.
426 pkg_index = GrabLocalPackageIndex(package_path)
427 pkg_index.SetUploadLocation(self._binhost_base_url, url_suffix)
David James615e5b52011-06-03 11:10:15 -0700428 pkg_index.RemoveFilteredPackages(self._ShouldFilterPackage)
David Jamesc0f158a2011-02-22 16:07:29 -0800429 uploads = pkg_index.ResolveDuplicateUploads(self._pkg_indexes)
David James05bcb2b2011-02-09 09:25:47 -0800430
David Jamesa3bba142011-05-26 21:24:20 -0700431 if not uploads:
432 return False
433
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
David Jamesa3bba142011-05-26 21:24:20 -0700463 return True
464
David James8fa34ea2011-04-15 13:00:20 -0700465 def _UploadBoardTarball(self, board_path, url_suffix):
466 """Upload a tarball of the board at the specified path to Google Storage.
467
468 Args:
469 board_path: The path to the board dir.
470 url_suffix: The remote subdirectory where we should upload the packages.
471 """
472 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
473 assert remote_location.startswith('gs://')
474 cwd, boardname = os.path.split(board_path.rstrip(os.path.sep))
475 tmpdir = tempfile.mkdtemp()
476 try:
477 tarfile = os.path.join(tmpdir, '%s.tbz2' % boardname)
478 cmd = ['sudo', 'tar', '-I', 'pbzip2', '-cf', tarfile]
479 excluded_paths = ('usr/lib/debug', 'usr/local/autotest', 'packages',
480 'tmp')
481 for path in excluded_paths:
482 cmd.append('--exclude=%s/%s/*' % (boardname, path))
483 cmd.append(boardname)
484 cros_build_lib.RunCommand(cmd, cwd=cwd)
485 remote_tarfile = '%s/%s.tbz2' % (remote_location.rstrip('/'), boardname)
486 if _GsUpload((tarfile, remote_tarfile, self._acl)):
487 sys.exit(1)
488 finally:
489 cros_build_lib.RunCommand(['sudo', 'rm', '-rf', tmpdir], cwd=cwd)
490
David James615e5b52011-06-03 11:10:15 -0700491 def _SyncHostPrebuilts(self, version, key, git_sync, sync_binhost_conf):
David Jamesc0f158a2011-02-22 16:07:29 -0800492 """Synchronize host prebuilt files.
David James05bcb2b2011-02-09 09:25:47 -0800493
David Jamesc0f158a2011-02-22 16:07:29 -0800494 This function will sync both the standard host packages, plus the host
495 packages associated with all targets that have been "setup" with the
496 current host's chroot. For instance, if this host has been used to build
497 x86-generic, it will sync the host packages associated with
498 'i686-pc-linux-gnu'. If this host has also been used to build arm-generic,
499 it will also sync the host packages associated with
500 'armv7a-cros-linux-gnueabi'.
David James05bcb2b2011-02-09 09:25:47 -0800501
David Jamesc0f158a2011-02-22 16:07:29 -0800502 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800503 version: A unique string, intended to be included in the upload path,
504 which identifies the version number of the uploaded prebuilts.
505 key: The variable key to update in the git file.
506 git_sync: If set, update make.conf of target to reference the latest
507 prebuilt packages generated here.
508 sync_binhost_conf: If set, update binhost config file in
509 chromiumos-overlay for the host.
510 """
511 # Upload prebuilts.
David James615e5b52011-06-03 11:10:15 -0700512 package_path = os.path.join(self._build_path, _HOST_PACKAGES_PATH)
David Jamesc0f158a2011-02-22 16:07:29 -0800513 url_suffix = _REL_HOST_PATH % {'version': version, 'target': _HOST_TARGET}
David James8fa34ea2011-04-15 13:00:20 -0700514 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James05bcb2b2011-02-09 09:25:47 -0800515
David Jamesa3bba142011-05-26 21:24:20 -0700516 if self._UploadPrebuilt(package_path, packages_url_suffix):
517 # Record URL where prebuilts were uploaded.
518 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
519 packages_url_suffix.rstrip('/'))
520 if git_sync:
David James615e5b52011-06-03 11:10:15 -0700521 git_file = os.path.join(self._build_path,
522 _PREBUILT_MAKE_CONF[_HOST_TARGET])
David Jamesa3bba142011-05-26 21:24:20 -0700523 RevGitFile(git_file, url_value, key=key)
524 if sync_binhost_conf:
David James615e5b52011-06-03 11:10:15 -0700525 binhost_conf = os.path.join(self._build_path, _BINHOST_CONF_DIR,
526 'host', '%s-%s.conf' % (_HOST_TARGET, key))
David Jamesa3bba142011-05-26 21:24:20 -0700527 UpdateBinhostConfFile(binhost_conf, key, url_value)
David Jamesc0f158a2011-02-22 16:07:29 -0800528
David James615e5b52011-06-03 11:10:15 -0700529 def _SyncBoardPrebuilts(self, board, version, key, git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700530 sync_binhost_conf, upload_board_tarball):
David Jamesc0f158a2011-02-22 16:07:29 -0800531 """Synchronize board prebuilt files.
532
533 Args:
534 board: The board to upload to Google Storage.
David Jamesc0f158a2011-02-22 16:07:29 -0800535 version: A unique string, intended to be included in the upload path,
536 which identifies the version number of the uploaded prebuilts.
537 key: The variable key to update in the git file.
538 git_sync: If set, update make.conf of target to reference the latest
539 prebuilt packages generated here.
540 sync_binhost_conf: If set, update binhost config file in
541 chromiumos-overlay for the current board.
David James8fa34ea2011-04-15 13:00:20 -0700542 upload_board_tarball: Include a tarball of the board in our upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800543 """
David James615e5b52011-06-03 11:10:15 -0700544 board_path = os.path.join(self._build_path, _BOARD_PATH % {'board': board})
David Jamesc0f158a2011-02-22 16:07:29 -0800545 package_path = os.path.join(board_path, 'packages')
546 url_suffix = _REL_BOARD_PATH % {'board': board, 'version': version}
David James8fa34ea2011-04-15 13:00:20 -0700547 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
548
549 # Upload board tarballs in the background.
550 if upload_board_tarball:
551 tar_process = multiprocessing.Process(target=self._UploadBoardTarball,
552 args=(board_path, url_suffix))
553 tar_process.start()
554
555 # Upload prebuilts.
David Jamesa3bba142011-05-26 21:24:20 -0700556 uploaded = self._UploadPrebuilt(package_path, packages_url_suffix)
David James8fa34ea2011-04-15 13:00:20 -0700557
558 # Make sure we finished uploading the board tarballs.
559 if upload_board_tarball:
560 tar_process.join()
561 assert tar_process.exitcode == 0
David Jamesc0f158a2011-02-22 16:07:29 -0800562
David Jamesa3bba142011-05-26 21:24:20 -0700563 if uploaded:
564 # Record URL where prebuilts were uploaded.
565 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
566 packages_url_suffix.rstrip('/'))
567 if git_sync:
David James615e5b52011-06-03 11:10:15 -0700568 git_file = DeterminePrebuiltConfFile(self._build_path, board)
David Jamesa3bba142011-05-26 21:24:20 -0700569 RevGitFile(git_file, url_value, key=key)
570 if sync_binhost_conf:
David James615e5b52011-06-03 11:10:15 -0700571 binhost_conf = os.path.join(self._build_path, _BINHOST_CONF_DIR,
572 'target', '%s-%s.conf' % (board, key))
David Jamesa3bba142011-05-26 21:24:20 -0700573 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800574
575
David James8c846492011-01-25 17:07:29 -0800576def usage(parser, msg):
577 """Display usage message and parser help then exit with 1."""
578 print >> sys.stderr, msg
579 parser.print_help()
580 sys.exit(1)
581
David Jamesc0f158a2011-02-22 16:07:29 -0800582def ParseOptions():
David James8c846492011-01-25 17:07:29 -0800583 parser = optparse.OptionParser()
584 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
585 default=_BINHOST_BASE_URL,
586 help='Base URL to use for binhost in make.conf updates')
587 parser.add_option('', '--previous-binhost-url', action='append',
588 default=[], dest='previous_binhost_url',
589 help='Previous binhost URL')
590 parser.add_option('-b', '--board', dest='board', default=None,
591 help='Board type that was built on this machine')
592 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800593 help='Path to the directory containing the chroot')
David James615e5b52011-06-03 11:10:15 -0700594 parser.add_option('', '--packages', action='append',
595 default=[], dest='packages',
596 help='Only include the specified packages. '
597 '(Default is to include all packages.)')
David James8c846492011-01-25 17:07:29 -0800598 parser.add_option('-s', '--sync-host', dest='sync_host',
599 default=False, action='store_true',
600 help='Sync host prebuilts')
601 parser.add_option('-g', '--git-sync', dest='git_sync',
602 default=False, action='store_true',
603 help='Enable git version sync (This commits to a repo)')
604 parser.add_option('-u', '--upload', dest='upload',
605 default=None,
606 help='Upload location')
607 parser.add_option('-V', '--prepend-version', dest='prepend_version',
608 default=None,
609 help='Add an identifier to the front of the version')
610 parser.add_option('-f', '--filters', dest='filters', action='store_true',
611 default=False,
612 help='Turn on filtering of private ebuild packages')
613 parser.add_option('-k', '--key', dest='key',
614 default='PORTAGE_BINHOST',
615 help='Key to update in make.conf / binhost.conf')
616 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
617 default=False, action='store_true',
618 help='Update binhost.conf')
David Jamesfd0b0852011-02-23 11:15:36 -0800619 parser.add_option('-P', '--private', dest='private', action='store_true',
620 default=False, help='Mark gs:// uploads as private.')
David James8fa34ea2011-04-15 13:00:20 -0700621 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
622 action='store_true', default=False,
623 help='Upload board tarball to Google Storage.')
David James8c846492011-01-25 17:07:29 -0800624
625 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800626 if not options.build_path:
627 usage(parser, 'Error: you need provide a chroot path')
David James8c846492011-01-25 17:07:29 -0800628 if not options.upload:
629 usage(parser, 'Error: you need to provide an upload location using -u')
David James9417f272011-05-26 13:24:47 -0700630 if args:
631 usage(parser, 'Error: invalid arguments passed to prebuilt.py: %r' % args)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700632
David James8fa34ea2011-04-15 13:00:20 -0700633
634 if options.upload_board_tarball and not options.upload.startswith('gs://'):
635 usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
636 '--upload must be a gs:// URL.')
637
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700638 if options.private:
639 if options.sync_host:
640 usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
641 'together, we do not support private host prebuilts')
642
643 if not options.upload.startswith('gs://'):
644 usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
645 '--upload must be a gs:// URL.')
646
647 if options.binhost_base_url != _BINHOST_BASE_URL:
648 usage(parser, 'Error: when using --private the --binhost-base-url '
649 'is automatically derived.')
David Jamesc0f158a2011-02-22 16:07:29 -0800650 return options
651
652def main():
653 options = ParseOptions()
654
David James05bcb2b2011-02-09 09:25:47 -0800655 # Calculate a list of Packages index files to compare against. Whenever we
656 # upload a package, we check to make sure it's not already stored in one of
657 # the packages files we uploaded. This list of packages files might contain
658 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800659 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800660
David Jamesc0f158a2011-02-22 16:07:29 -0800661 version = GetVersion()
662 if options.prepend_version:
663 version = '%s-%s' % (options.prepend_version, version)
664
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700665 acl = 'public-read'
666 binhost_base_url = options.binhost_base_url
667
668 if options.private:
669 binhost_base_url = options.upload
670 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
671 options.board)
672 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
673
674 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David James615e5b52011-06-03 11:10:15 -0700675 pkg_indexes, options.build_path,
676 options.packages)
David Jamesc0f158a2011-02-22 16:07:29 -0800677
David James8c846492011-01-25 17:07:29 -0800678 if options.sync_host:
David James615e5b52011-06-03 11:10:15 -0700679 uploader._SyncHostPrebuilts(version, options.key, options.git_sync,
680 options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800681
682 if options.board:
David James615e5b52011-06-03 11:10:15 -0700683 uploader._SyncBoardPrebuilts(options.board, version,
David Jamesc0f158a2011-02-22 16:07:29 -0800684 options.key, options.git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700685 options.sync_binhost_conf,
686 options.upload_board_tarball)
David James8c846492011-01-25 17:07:29 -0800687
688if __name__ == '__main__':
689 main()