blob: 7b2e6eac3e329e9e869226cc601235107c65e1a3 [file] [log] [blame]
David James8c846492011-01-25 17:07:29 -08001#!/usr/bin/python
2# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
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
20from chromite.lib.binpkg import (GrabLocalPackageIndex, GrabRemotePackageIndex,
21 PackageIndex)
22"""
23This script is used to upload host prebuilts as well as board BINHOSTS.
24
25If the URL starts with 'gs://', we upload using gsutil to Google Storage.
26Otherwise, rsync is used.
27
28After a build is successfully uploaded a file is updated with the proper
29BINHOST version as well as the target board. This file is defined in GIT_FILE
30
31
32To read more about prebuilts/binhost binary packages please refer to:
33http://sites/chromeos/for-team-members/engineering/releng/prebuilt-binaries-for-streamlining-the-build-process
34
35
36Example of uploading prebuilt amd64 host files to Google Storage:
37./prebuilt.py -p /b/cbuild/build -s -u gs://chromeos-prebuilt
38
39Example of uploading x86-dogfood binhosts to Google Storage:
40./prebuilt.py -b x86-dogfood -p /b/cbuild/build/ -u gs://chromeos-prebuilt -g
41
42Example of uploading prebuilt amd64 host files using rsync:
43./prebuilt.py -p /b/cbuild/build -s -u codf30.jail:/tmp
44"""
45
46# as per http://crosbug.com/5855 always filter the below packages
47_FILTER_PACKAGES = set()
48_RETRIES = 3
49_GSUTIL_BIN = '/b/build/third_party/gsutil/gsutil'
50_HOST_PACKAGES_PATH = 'chroot/var/lib/portage/pkgs'
David James05bcb2b2011-02-09 09:25:47 -080051_CATEGORIES_PATH = 'chroot/etc/portage/categories'
David James8c846492011-01-25 17:07:29 -080052_HOST_TARGET = 'amd64'
53_BOARD_PATH = 'chroot/build/%(board)s'
54_BOTO_CONFIG = '/home/chrome-bot/external-boto'
55# board/board-target/version/packages/'
56_REL_BOARD_PATH = 'board/%(board)s/%(version)s/packages'
57# host/host-target/version/packages/'
58_REL_HOST_PATH = 'host/%(target)s/%(version)s/packages'
59# Private overlays to look at for builds to filter
60# relative to build path
61_PRIVATE_OVERLAY_DIR = 'src/private-overlays'
62_BINHOST_BASE_URL = 'http://commondatastorage.googleapis.com/chromeos-prebuilt'
63_PREBUILT_BASE_DIR = 'src/third_party/chromiumos-overlay/chromeos/config/'
64# Created in the event of new host targets becoming available
65_PREBUILT_MAKE_CONF = {'amd64': os.path.join(_PREBUILT_BASE_DIR,
66 'make.conf.amd64-host')}
67_BINHOST_CONF_DIR = 'src/third_party/chromiumos-overlay/chromeos/binhost'
68
69
70class FiltersEmpty(Exception):
71 """Raised when filters are used but none are found."""
72 pass
73
74
75class UploadFailed(Exception):
76 """Raised when one of the files uploaded failed."""
77 pass
78
79class UnknownBoardFormat(Exception):
80 """Raised when a function finds an unknown board format."""
81 pass
82
83class GitPushFailed(Exception):
84 """Raised when a git push failed after retry."""
85 pass
86
87
88def UpdateLocalFile(filename, value, key='PORTAGE_BINHOST'):
89 """Update the key in file with the value passed.
90 File format:
91 key="value"
92 Note quotes are added automatically
93
94 Args:
95 filename: Name of file to modify.
96 value: Value to write with the key.
97 key: The variable key to update. (Default: PORTAGE_BINHOST)
98 """
99 if os.path.exists(filename):
100 file_fh = open(filename)
101 else:
102 file_fh = open(filename, 'w+')
103 file_lines = []
104 found = False
105 keyval_str = '%(key)s=%(value)s'
106 for line in file_fh:
107 # Strip newlines from end of line. We already add newlines below.
108 line = line.rstrip("\n")
109
110 if len(line.split('=')) != 2:
111 # Skip any line that doesn't fit key=val.
112 file_lines.append(line)
113 continue
114
115 file_var, file_val = line.split('=')
116 if file_var == key:
117 found = True
118 print 'Updating %s=%s to %s="%s"' % (file_var, file_val, key, value)
119 value = '"%s"' % value
120 file_lines.append(keyval_str % {'key': key, 'value': value})
121 else:
122 file_lines.append(keyval_str % {'key': file_var, 'value': file_val})
123
124 if not found:
125 file_lines.append(keyval_str % {'key': key, 'value': value})
126
127 file_fh.close()
128 # write out new file
129 new_file_fh = open(filename, 'w')
130 new_file_fh.write('\n'.join(file_lines) + '\n')
131 new_file_fh.close()
132
133
134def RevGitPushWithRetry(retries=5):
135 """Repo sync and then push git changes in flight.
136
137 Args:
138 retries: The number of times to retry before giving up, default: 5
139
140 Raises:
141 GitPushFailed if push was unsuccessful after retries
142 """
Chris Sosa471532a2011-02-01 15:10:06 -0800143 for retry in range(1, retries + 1):
David James8c846492011-01-25 17:07:29 -0800144 try:
145 cros_build_lib.RunCommand('repo sync .', shell=True)
146 cros_build_lib.RunCommand('git push', shell=True)
147 break
148 except cros_build_lib.RunCommandError:
149 if retry < retries:
150 print 'Error pushing changes trying again (%s/%s)' % (retry, retries)
Chris Sosa471532a2011-02-01 15:10:06 -0800151 time.sleep(5 * retry)
David James8c846492011-01-25 17:07:29 -0800152 else:
153 raise GitPushFailed('Failed to push change after %s retries' % retries)
154
155
156def RevGitFile(filename, value, retries=5, key='PORTAGE_BINHOST'):
157 """Update and push the git file.
158
159 Args:
160 filename: file to modify that is in a git repo already
161 value: string representing the version of the prebuilt that has been
162 uploaded.
163 retries: The number of times to retry before giving up, default: 5
164 key: The variable key to update in the git file.
165 (Default: PORTAGE_BINHOST)
166 """
167 prebuilt_branch = 'prebuilt_branch'
168 old_cwd = os.getcwd()
169 os.chdir(os.path.dirname(filename))
170
David James518d23e2011-02-23 15:39:55 -0800171 commit = cros_build_lib.RunCommand('git rev-parse HEAD', shell=True,
172 redirect_stdout=True).output
173 cros_build_lib.RunCommand('git remote update', shell=True)
David James8c846492011-01-25 17:07:29 -0800174 cros_build_lib.RunCommand('repo start %s .' % prebuilt_branch, shell=True)
175 git_ssh_config_cmd = (
176 'git config url.ssh://git@gitrw.chromium.org:9222.pushinsteadof '
177 'http://git.chromium.org/git')
178 cros_build_lib.RunCommand(git_ssh_config_cmd, shell=True)
179 description = 'Update %s="%s" in %s' % (key, value, filename)
180 print description
181 try:
182 UpdateLocalFile(filename, value, key)
183 cros_build_lib.RunCommand('git config push.default tracking', shell=True)
184 cros_build_lib.RunCommand('git commit -am "%s"' % description, shell=True)
185 RevGitPushWithRetry(retries)
186 finally:
187 cros_build_lib.RunCommand('repo abandon %s .' % prebuilt_branch, shell=True)
David James518d23e2011-02-23 15:39:55 -0800188 cros_build_lib.RunCommand('git checkout %s' % commit, shell=True)
David James8c846492011-01-25 17:07:29 -0800189 os.chdir(old_cwd)
190
191
192def GetVersion():
193 """Get the version to put in LATEST and update the git version with."""
194 return datetime.datetime.now().strftime('%d.%m.%y.%H%M%S')
195
196
197def LoadPrivateFilters(build_path):
198 """Load private filters based on ebuilds found under _PRIVATE_OVERLAY_DIR.
199
200 This function adds filters to the global set _FILTER_PACKAGES.
201 Args:
202 build_path: Path that _PRIVATE_OVERLAY_DIR is in.
203 """
204 # TODO(scottz): eventually use manifest.xml to find the proper
205 # private overlay path.
206 filter_path = os.path.join(build_path, _PRIVATE_OVERLAY_DIR)
207 files = cros_build_lib.ListFiles(filter_path)
208 filters = []
209 for file in files:
210 if file.endswith('.ebuild'):
211 basename = os.path.basename(file)
212 match = re.match('(.*?)-\d.*.ebuild', basename)
213 if match:
214 filters.append(match.group(1))
215
216 if not filters:
217 raise FiltersEmpty('No filters were returned')
218
219 _FILTER_PACKAGES.update(filters)
220
221
222def ShouldFilterPackage(file_path):
223 """Skip a particular file if it matches a pattern.
224
225 Skip any files that machine the list of packages to filter in
226 _FILTER_PACKAGES.
227
228 Args:
229 file_path: string of a file path to inspect against _FILTER_PACKAGES
230
231 Returns:
232 True if we should filter the package,
233 False otherwise.
234 """
235 for name in _FILTER_PACKAGES:
236 if name in file_path:
237 print 'FILTERING %s' % file_path
238 return True
239
240 return False
241
242
243def _RetryRun(cmd, print_cmd=True, shell=False, cwd=None):
244 """Run the specified command, retrying if necessary.
245
246 Args:
247 cmd: The command to run.
248 print_cmd: Whether to print out the cmd.
249 shell: Whether to treat the command as a shell.
250 cwd: Working directory to run command in.
251
252 Returns:
253 True if the command succeeded. Otherwise, returns False.
254 """
255
256 # TODO(scottz): port to use _Run or similar when it is available in
257 # cros_build_lib.
258 for attempt in range(_RETRIES):
259 try:
260 output = cros_build_lib.RunCommand(cmd, print_cmd=print_cmd, shell=shell,
261 cwd=cwd)
262 return True
263 except cros_build_lib.RunCommandError:
264 print 'Failed to run %s' % cmd
265 else:
266 print 'Retry failed run %s, giving up' % cmd
267 return False
268
269
270def _GsUpload(args):
271 """Upload to GS bucket.
272
273 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800274 args: a tuple of three arguments that contains local_file, remote_file, and
275 the acl used for uploading the file.
David James8c846492011-01-25 17:07:29 -0800276
277 Returns:
278 Return the arg tuple of two if the upload failed
279 """
David Jamesfd0b0852011-02-23 11:15:36 -0800280 (local_file, remote_file, acl) = args
David James8c846492011-01-25 17:07:29 -0800281
David Jamesfd0b0852011-02-23 11:15:36 -0800282 cmd = '%s cp -a %s %s %s' % (_GSUTIL_BIN, acl, local_file, remote_file)
David James8c846492011-01-25 17:07:29 -0800283 if not _RetryRun(cmd, print_cmd=False, shell=True):
284 return (local_file, remote_file)
285
David Jamesfd0b0852011-02-23 11:15:36 -0800286def RemoteUpload(acl, files, pool=10):
David James8c846492011-01-25 17:07:29 -0800287 """Upload to google storage.
288
289 Create a pool of process and call _GsUpload with the proper arguments.
290
291 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800292 acl: The canned acl used for uploading. acl can be one of: "public-read",
293 "public-read-write", "authenticated-read", "bucket-owner-read",
294 "bucket-owner-full-control", or "private".
David James8c846492011-01-25 17:07:29 -0800295 files: dictionary with keys to local files and values to remote path.
296 pool: integer of maximum proesses to have at the same time.
297
298 Returns:
299 Return a set of tuple arguments of the failed uploads
300 """
301 # TODO(scottz) port this to use _RunManyParallel when it is available in
302 # cros_build_lib
303 pool = multiprocessing.Pool(processes=pool)
304 workers = []
305 for local_file, remote_path in files.iteritems():
David Jamesfd0b0852011-02-23 11:15:36 -0800306 workers.append((local_file, remote_path, acl))
David James8c846492011-01-25 17:07:29 -0800307
308 result = pool.map_async(_GsUpload, workers, chunksize=1)
309 while True:
310 try:
Chris Sosa471532a2011-02-01 15:10:06 -0800311 return set(result.get(60 * 60))
David James8c846492011-01-25 17:07:29 -0800312 except multiprocessing.TimeoutError:
313 pass
314
315
316def GenerateUploadDict(base_local_path, base_remote_path, pkgs):
317 """Build a dictionary of local remote file key pairs to upload.
318
319 Args:
320 base_local_path: The base path to the files on the local hard drive.
321 remote_path: The base path to the remote paths.
322 pkgs: The packages to upload.
323
324 Returns:
325 Returns a dictionary of local_path/remote_path pairs
326 """
327 upload_files = {}
328 for pkg in pkgs:
329 suffix = pkg['CPV'] + '.tbz2'
330 local_path = os.path.join(base_local_path, suffix)
331 assert os.path.exists(local_path)
332 remote_path = '%s/%s' % (base_remote_path.rstrip('/'), suffix)
333 upload_files[local_path] = remote_path
334
335 return upload_files
336
337def GetBoardPathFromCrosOverlayList(build_path, target):
338 """Use the cros_overlay_list to determine the path to the board overlay
339 Args:
340 build_path: The path to the root of the build directory
341 target: The target that we are looking for, could consist of board and
342 board_variant, we handle that properly
343 Returns:
344 The last line from cros_overlay_list as a string
345 """
Chris Sosa471532a2011-02-01 15:10:06 -0800346 script_dir = os.path.join(build_path, 'src/platform/dev/host')
David James8c846492011-01-25 17:07:29 -0800347 cmd = ['./cros_overlay_list']
348 if re.match('.*?_.*', target):
349 (board, variant) = target.split('_')
350 cmd += ['--board', board, '--variant', variant]
351 elif re.match('.*?-\w+', target):
352 cmd += ['--board', target]
353 else:
354 raise UnknownBoardFormat('Unknown format: %s' % target)
355
356 cmd_output = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
357 cwd=script_dir)
358 # We only care about the last entry
359 return cmd_output.output.splitlines().pop()
360
361
362def DeterminePrebuiltConfFile(build_path, target):
363 """Determine the prebuilt.conf file that needs to be updated for prebuilts.
364
365 Args:
366 build_path: The path to the root of the build directory
367 target: String representation of the board. This includes host and board
368 targets
369
370 Returns
371 A string path to a prebuilt.conf file to be updated.
372 """
373 if _HOST_TARGET == target:
374 # We are host.
375 # Without more examples of hosts this is a kludge for now.
376 # TODO(Scottz): as new host targets come online expand this to
377 # work more like boards.
Chris Sosa471532a2011-02-01 15:10:06 -0800378 make_path = _PREBUILT_MAKE_CONF[target]
David James8c846492011-01-25 17:07:29 -0800379 else:
380 # We are a board
381 board = GetBoardPathFromCrosOverlayList(build_path, target)
382 make_path = os.path.join(board, 'prebuilt.conf')
383
384 return make_path
385
386
387def UpdateBinhostConfFile(path, key, value):
388 """Update binhost config file file with key=value.
389
390 Args:
391 path: Filename to update.
392 key: Key to update.
393 value: New value for key.
394 """
395 cwd = os.path.dirname(os.path.abspath(path))
396 filename = os.path.basename(path)
397 if not os.path.isdir(cwd):
398 os.makedirs(cwd)
399 if not os.path.isfile(path):
400 config_file = file(path, 'w')
David James8c846492011-01-25 17:07:29 -0800401 config_file.close()
402 UpdateLocalFile(path, value, key)
403 cros_build_lib.RunCommand('git add %s' % filename, cwd=cwd, shell=True)
404 description = 'Update %s=%s in %s' % (key, value, filename)
405 cros_build_lib.RunCommand('git commit -m "%s"' % description, cwd=cwd,
406 shell=True)
407
408
David Jamesce093af2011-02-23 15:21:58 -0800409def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800410 """Grab all of the packages files associated with a list of binhost_urls.
411
David James05bcb2b2011-02-09 09:25:47 -0800412 Args:
413 binhost_urls: The URLs for the directories containing the Packages files we
414 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800415
416 Returns:
417 A list of PackageIndex objects.
418 """
419 pkg_indexes = []
420 for url in binhost_urls:
421 pkg_index = GrabRemotePackageIndex(url)
422 if pkg_index:
423 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800424 return pkg_indexes
425
426
David James05bcb2b2011-02-09 09:25:47 -0800427
David Jamesc0f158a2011-02-22 16:07:29 -0800428class PrebuiltUploader(object):
429 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800430
David Jamesfd0b0852011-02-23 11:15:36 -0800431 def __init__(self, upload_location, acl, binhost_base_url, pkg_indexes):
David Jamesc0f158a2011-02-22 16:07:29 -0800432 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800433
David Jamesc0f158a2011-02-22 16:07:29 -0800434 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800435
David Jamesc0f158a2011-02-22 16:07:29 -0800436 Args:
437 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800438 acl: The canned acl used for uploading to Google Storage. acl can be one
439 of: "public-read", "public-read-write", "authenticated-read",
440 "bucket-owner-read", "bucket-owner-full-control", or "private". If
441 we are not uploading to Google Storage, this parameter is unused.
442 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800443 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
444 uploading duplicate files, we just link to the old files.
445 """
446 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800447 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800448 self._binhost_base_url = binhost_base_url
449 self._pkg_indexes = pkg_indexes
David James8c846492011-01-25 17:07:29 -0800450
David Jamesc0f158a2011-02-22 16:07:29 -0800451 def _UploadPrebuilt(self, package_path, url_suffix):
452 """Upload host or board prebuilt files to Google Storage space.
David James8c846492011-01-25 17:07:29 -0800453
David Jamesc0f158a2011-02-22 16:07:29 -0800454 Args:
455 package_path: The path to the packages dir.
David Jamesce093af2011-02-23 15:21:58 -0800456 url_suffix: The remote subdirectory where we should upload the packages.
David Jamesc0f158a2011-02-22 16:07:29 -0800457 """
David James8c846492011-01-25 17:07:29 -0800458
David Jamesc0f158a2011-02-22 16:07:29 -0800459 # Process Packages file, removing duplicates and filtered packages.
460 pkg_index = GrabLocalPackageIndex(package_path)
461 pkg_index.SetUploadLocation(self._binhost_base_url, url_suffix)
462 pkg_index.RemoveFilteredPackages(ShouldFilterPackage)
463 uploads = pkg_index.ResolveDuplicateUploads(self._pkg_indexes)
David James05bcb2b2011-02-09 09:25:47 -0800464
David Jamesc0f158a2011-02-22 16:07:29 -0800465 # Write Packages file.
466 tmp_packages_file = pkg_index.WriteToNamedTemporaryFile()
David James05bcb2b2011-02-09 09:25:47 -0800467
David Jamesc0f158a2011-02-22 16:07:29 -0800468 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
469 if remote_location.startswith('gs://'):
470 # Build list of files to upload.
471 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
472 remote_file = '%s/Packages' % remote_location.rstrip('/')
473 upload_files[tmp_packages_file.name] = remote_file
David James05bcb2b2011-02-09 09:25:47 -0800474
David Jamesfd0b0852011-02-23 11:15:36 -0800475 failed_uploads = RemoteUpload(self._acl, upload_files)
David Jamesc0f158a2011-02-22 16:07:29 -0800476 if len(failed_uploads) > 1 or (None not in failed_uploads):
477 error_msg = ['%s -> %s\n' % args for args in failed_uploads]
478 raise UploadFailed('Error uploading:\n%s' % error_msg)
479 else:
480 pkgs = ' '.join(p['CPV'] + '.tbz2' for p in uploads)
481 ssh_server, remote_path = remote_location.split(':', 1)
482 d = { 'pkg_index': tmp_packages_file.name,
483 'pkgs': pkgs,
484 'remote_packages': '%s/Packages' % remote_location.rstrip('/'),
485 'remote_path': remote_path.rstrip('/'),
486 'remote_location': remote_location.rstrip('/'),
487 'ssh_server': ssh_server }
488 cmds = ['ssh %(ssh_server)s mkdir -p %(remote_path)s' % d,
489 'rsync -av --chmod=a+r %(pkg_index)s %(remote_packages)s' % d]
490 if pkgs:
491 cmds.append('rsync -Rav %(pkgs)s %(remote_location)s/' % d)
492 for cmd in cmds:
493 if not _RetryRun(cmd, shell=True, cwd=package_path):
494 raise UploadFailed('Could not run %s' % cmd)
David James8c846492011-01-25 17:07:29 -0800495
David Jamesc0f158a2011-02-22 16:07:29 -0800496 def _SyncHostPrebuilts(self, build_path, version, key, git_sync,
497 sync_binhost_conf):
498 """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:
509 build_path: The path to the directory containing the chroot.
510 version: A unique string, intended to be included in the upload path,
511 which identifies the version number of the uploaded prebuilts.
512 key: The variable key to update in the git file.
513 git_sync: If set, update make.conf of target to reference the latest
514 prebuilt packages generated here.
515 sync_binhost_conf: If set, update binhost config file in
516 chromiumos-overlay for the host.
517 """
518 # Upload prebuilts.
519 package_path = os.path.join(build_path, _HOST_PACKAGES_PATH)
520 url_suffix = _REL_HOST_PATH % {'version': version, 'target': _HOST_TARGET}
David Jamesc0f158a2011-02-22 16:07:29 -0800521 self._UploadPrebuilt(package_path, url_suffix)
David James05bcb2b2011-02-09 09:25:47 -0800522
David Jamesc0f158a2011-02-22 16:07:29 -0800523 # Record URL where prebuilts were uploaded.
524 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
525 url_suffix.rstrip('/'))
526 if git_sync:
527 git_file = os.path.join(build_path, _PREBUILT_MAKE_CONF[_HOST_TARGET])
528 RevGitFile(git_file, url_value, key=key)
529 if sync_binhost_conf:
530 binhost_conf = os.path.join(build_path, _BINHOST_CONF_DIR, 'host',
531 '%s-%s.conf' % (_HOST_TARGET, key))
532 UpdateBinhostConfFile(binhost_conf, key, url_value)
533
534 def _SyncBoardPrebuilts(self, board, build_path, version, key, git_sync,
535 sync_binhost_conf):
536 """Synchronize board prebuilt files.
537
538 Args:
539 board: The board to upload to Google Storage.
540 build_path: The path to the directory containing the chroot.
541 version: A unique string, intended to be included in the upload path,
542 which identifies the version number of the uploaded prebuilts.
543 key: The variable key to update in the git file.
544 git_sync: If set, update make.conf of target to reference the latest
545 prebuilt packages generated here.
546 sync_binhost_conf: If set, update binhost config file in
547 chromiumos-overlay for the current board.
548 """
549 # Upload prebuilts.
550 board_path = os.path.join(build_path, _BOARD_PATH % {'board': board})
551 package_path = os.path.join(board_path, 'packages')
552 url_suffix = _REL_BOARD_PATH % {'board': board, 'version': version}
553 self._UploadPrebuilt(package_path, url_suffix)
554
555 # Record URL where prebuilts were uploaded.
556 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
557 url_suffix.rstrip('/'))
558 if git_sync:
559 git_file = DeterminePrebuiltConfFile(build_path, board)
560 RevGitFile(git_file, url_value, key=key)
561 if sync_binhost_conf:
562 binhost_conf = os.path.join(build_path, _BINHOST_CONF_DIR, 'target',
563 '%s-%s.conf' % (board, key))
564 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800565
566
David James8c846492011-01-25 17:07:29 -0800567def usage(parser, msg):
568 """Display usage message and parser help then exit with 1."""
569 print >> sys.stderr, msg
570 parser.print_help()
571 sys.exit(1)
572
David Jamesc0f158a2011-02-22 16:07:29 -0800573def ParseOptions():
David James8c846492011-01-25 17:07:29 -0800574 parser = optparse.OptionParser()
575 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
576 default=_BINHOST_BASE_URL,
577 help='Base URL to use for binhost in make.conf updates')
578 parser.add_option('', '--previous-binhost-url', action='append',
579 default=[], dest='previous_binhost_url',
580 help='Previous binhost URL')
581 parser.add_option('-b', '--board', dest='board', default=None,
582 help='Board type that was built on this machine')
583 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800584 help='Path to the directory containing the chroot')
David James8c846492011-01-25 17:07:29 -0800585 parser.add_option('-s', '--sync-host', dest='sync_host',
586 default=False, action='store_true',
587 help='Sync host prebuilts')
588 parser.add_option('-g', '--git-sync', dest='git_sync',
589 default=False, action='store_true',
590 help='Enable git version sync (This commits to a repo)')
591 parser.add_option('-u', '--upload', dest='upload',
592 default=None,
593 help='Upload location')
594 parser.add_option('-V', '--prepend-version', dest='prepend_version',
595 default=None,
596 help='Add an identifier to the front of the version')
597 parser.add_option('-f', '--filters', dest='filters', action='store_true',
598 default=False,
599 help='Turn on filtering of private ebuild packages')
600 parser.add_option('-k', '--key', dest='key',
601 default='PORTAGE_BINHOST',
602 help='Key to update in make.conf / binhost.conf')
603 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
604 default=False, action='store_true',
605 help='Update binhost.conf')
David Jamesfd0b0852011-02-23 11:15:36 -0800606 parser.add_option('-P', '--private', dest='private', action='store_true',
607 default=False, help='Mark gs:// uploads as private.')
David James8c846492011-01-25 17:07:29 -0800608
609 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800610 if not options.build_path:
611 usage(parser, 'Error: you need provide a chroot path')
David James8c846492011-01-25 17:07:29 -0800612 if not options.upload:
613 usage(parser, 'Error: you need to provide an upload location using -u')
David Jamesfd0b0852011-02-23 11:15:36 -0800614 if options.private and not (options.binhost_base_url.startswith('gs://') and
615 options.upload.startswith('gs://')):
616 usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
617 'Both --binhost-base-url and --upload must be gs:// URLs.')
David Jamesc0f158a2011-02-22 16:07:29 -0800618 return options
619
620def main():
621 options = ParseOptions()
622
623 # Setup boto environment for gsutil to use
624 os.environ['BOTO_CONFIG'] = _BOTO_CONFIG
David James8c846492011-01-25 17:07:29 -0800625
626 if options.filters:
627 LoadPrivateFilters(options.build_path)
628
David Jamesfd0b0852011-02-23 11:15:36 -0800629 acl = 'public-read'
630 if options.private:
631 acl = 'private'
632
David James05bcb2b2011-02-09 09:25:47 -0800633 # Calculate a list of Packages index files to compare against. Whenever we
634 # upload a package, we check to make sure it's not already stored in one of
635 # the packages files we uploaded. This list of packages files might contain
636 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800637 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800638
David Jamesc0f158a2011-02-22 16:07:29 -0800639 version = GetVersion()
640 if options.prepend_version:
641 version = '%s-%s' % (options.prepend_version, version)
642
David Jamesfd0b0852011-02-23 11:15:36 -0800643 uploader = PrebuiltUploader(options.upload, acl, options.binhost_base_url,
David Jamesc0f158a2011-02-22 16:07:29 -0800644 pkg_indexes)
645
David James8c846492011-01-25 17:07:29 -0800646 if options.sync_host:
David Jamesc0f158a2011-02-22 16:07:29 -0800647 uploader._SyncHostPrebuilts(options.build_path, version, options.key,
648 options.git_sync, options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800649
650 if options.board:
David Jamesc0f158a2011-02-22 16:07:29 -0800651 uploader._SyncBoardPrebuilts(options.board, options.build_path, version,
652 options.key, options.git_sync,
653 options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800654
655if __name__ == '__main__':
656 main()