blob: 7c2652e63632003e08a50def726522d6d4e8c7eb [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'
David James8fa34ea2011-04-15 13:00:20 -070054# board/board-target/version/'
55_REL_BOARD_PATH = 'board/%(board)s/%(version)s'
56# host/host-target/version/'
57_REL_HOST_PATH = 'host/%(target)s/%(version)s'
David James8c846492011-01-25 17:07:29 -080058# Private overlays to look at for builds to filter
59# relative to build path
60_PRIVATE_OVERLAY_DIR = 'src/private-overlays'
Scott Zawalskiab1bed32011-03-16 15:24:24 -070061_GOOGLESTORAGE_ACL_FILE = 'googlestorage_acl.xml'
David James8c846492011-01-25 17:07:29 -080062_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
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700281 CANNED_ACLS = ['public-read', 'private', 'bucket-owner-read',
282 'authenticated-read', 'bucket-owner-full-control',
283 'public-read-write']
284 acl_cmd = None
285 if acl in CANNED_ACLS:
286 cmd = '%s cp -a %s %s %s' % (_GSUTIL_BIN, acl, local_file, remote_file)
287 else:
288 # For private uploads we assume that the overlay board is set up properly
289 # and a googlestore_acl.xml is present, if not this script errors
290 cmd = '%s cp -a private %s %s' % (_GSUTIL_BIN, local_file, remote_file)
291 if not os.path.exists(acl):
292 print >> sys.stderr, ('You are specifying either a file that does not '
293 'exist or an unknown canned acl: %s. Aborting '
294 'upload') % acl
295 # emulate the failing of an upload since we are not uploading the file
296 return (local_file, remote_file)
David James8c846492011-01-25 17:07:29 -0800297
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700298 acl_cmd = '%s setacl %s %s' % (_GSUTIL_BIN, acl, remote_file)
299
David James8c846492011-01-25 17:07:29 -0800300 if not _RetryRun(cmd, print_cmd=False, shell=True):
301 return (local_file, remote_file)
302
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700303 if acl_cmd:
304 # Apply the passed in ACL xml file to the uploaded object.
305 _RetryRun(acl_cmd, print_cmd=False, shell=True)
306
307
David Jamesfd0b0852011-02-23 11:15:36 -0800308def RemoteUpload(acl, files, pool=10):
David James8c846492011-01-25 17:07:29 -0800309 """Upload to google storage.
310
311 Create a pool of process and call _GsUpload with the proper arguments.
312
313 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800314 acl: The canned acl used for uploading. acl can be one of: "public-read",
315 "public-read-write", "authenticated-read", "bucket-owner-read",
316 "bucket-owner-full-control", or "private".
David James8c846492011-01-25 17:07:29 -0800317 files: dictionary with keys to local files and values to remote path.
318 pool: integer of maximum proesses to have at the same time.
319
320 Returns:
321 Return a set of tuple arguments of the failed uploads
322 """
323 # TODO(scottz) port this to use _RunManyParallel when it is available in
324 # cros_build_lib
325 pool = multiprocessing.Pool(processes=pool)
326 workers = []
327 for local_file, remote_path in files.iteritems():
David Jamesfd0b0852011-02-23 11:15:36 -0800328 workers.append((local_file, remote_path, acl))
David James8c846492011-01-25 17:07:29 -0800329
330 result = pool.map_async(_GsUpload, workers, chunksize=1)
331 while True:
332 try:
Chris Sosa471532a2011-02-01 15:10:06 -0800333 return set(result.get(60 * 60))
David James8c846492011-01-25 17:07:29 -0800334 except multiprocessing.TimeoutError:
335 pass
336
337
338def GenerateUploadDict(base_local_path, base_remote_path, pkgs):
339 """Build a dictionary of local remote file key pairs to upload.
340
341 Args:
342 base_local_path: The base path to the files on the local hard drive.
343 remote_path: The base path to the remote paths.
344 pkgs: The packages to upload.
345
346 Returns:
347 Returns a dictionary of local_path/remote_path pairs
348 """
349 upload_files = {}
350 for pkg in pkgs:
351 suffix = pkg['CPV'] + '.tbz2'
352 local_path = os.path.join(base_local_path, suffix)
353 assert os.path.exists(local_path)
354 remote_path = '%s/%s' % (base_remote_path.rstrip('/'), suffix)
355 upload_files[local_path] = remote_path
356
357 return upload_files
358
359def GetBoardPathFromCrosOverlayList(build_path, target):
360 """Use the cros_overlay_list to determine the path to the board overlay
361 Args:
362 build_path: The path to the root of the build directory
363 target: The target that we are looking for, could consist of board and
364 board_variant, we handle that properly
365 Returns:
366 The last line from cros_overlay_list as a string
367 """
Chris Sosa471532a2011-02-01 15:10:06 -0800368 script_dir = os.path.join(build_path, 'src/platform/dev/host')
David James8c846492011-01-25 17:07:29 -0800369 cmd = ['./cros_overlay_list']
370 if re.match('.*?_.*', target):
371 (board, variant) = target.split('_')
372 cmd += ['--board', board, '--variant', variant]
373 elif re.match('.*?-\w+', target):
374 cmd += ['--board', target]
375 else:
376 raise UnknownBoardFormat('Unknown format: %s' % target)
377
378 cmd_output = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
379 cwd=script_dir)
380 # We only care about the last entry
381 return cmd_output.output.splitlines().pop()
382
383
384def DeterminePrebuiltConfFile(build_path, target):
385 """Determine the prebuilt.conf file that needs to be updated for prebuilts.
386
387 Args:
388 build_path: The path to the root of the build directory
389 target: String representation of the board. This includes host and board
390 targets
391
392 Returns
393 A string path to a prebuilt.conf file to be updated.
394 """
395 if _HOST_TARGET == target:
396 # We are host.
397 # Without more examples of hosts this is a kludge for now.
398 # TODO(Scottz): as new host targets come online expand this to
399 # work more like boards.
Chris Sosa471532a2011-02-01 15:10:06 -0800400 make_path = _PREBUILT_MAKE_CONF[target]
David James8c846492011-01-25 17:07:29 -0800401 else:
402 # We are a board
403 board = GetBoardPathFromCrosOverlayList(build_path, target)
404 make_path = os.path.join(board, 'prebuilt.conf')
405
406 return make_path
407
408
409def UpdateBinhostConfFile(path, key, value):
410 """Update binhost config file file with key=value.
411
412 Args:
413 path: Filename to update.
414 key: Key to update.
415 value: New value for key.
416 """
417 cwd = os.path.dirname(os.path.abspath(path))
418 filename = os.path.basename(path)
419 if not os.path.isdir(cwd):
420 os.makedirs(cwd)
421 if not os.path.isfile(path):
422 config_file = file(path, 'w')
David James8c846492011-01-25 17:07:29 -0800423 config_file.close()
424 UpdateLocalFile(path, value, key)
425 cros_build_lib.RunCommand('git add %s' % filename, cwd=cwd, shell=True)
426 description = 'Update %s=%s in %s' % (key, value, filename)
427 cros_build_lib.RunCommand('git commit -m "%s"' % description, cwd=cwd,
428 shell=True)
429
430
David Jamesce093af2011-02-23 15:21:58 -0800431def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800432 """Grab all of the packages files associated with a list of binhost_urls.
433
David James05bcb2b2011-02-09 09:25:47 -0800434 Args:
435 binhost_urls: The URLs for the directories containing the Packages files we
436 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800437
438 Returns:
439 A list of PackageIndex objects.
440 """
441 pkg_indexes = []
442 for url in binhost_urls:
443 pkg_index = GrabRemotePackageIndex(url)
444 if pkg_index:
445 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800446 return pkg_indexes
447
448
David James05bcb2b2011-02-09 09:25:47 -0800449
David Jamesc0f158a2011-02-22 16:07:29 -0800450class PrebuiltUploader(object):
451 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800452
David Jamesfd0b0852011-02-23 11:15:36 -0800453 def __init__(self, upload_location, acl, binhost_base_url, pkg_indexes):
David Jamesc0f158a2011-02-22 16:07:29 -0800454 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800455
David Jamesc0f158a2011-02-22 16:07:29 -0800456 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800457
David Jamesc0f158a2011-02-22 16:07:29 -0800458 Args:
459 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800460 acl: The canned acl used for uploading to Google Storage. acl can be one
461 of: "public-read", "public-read-write", "authenticated-read",
462 "bucket-owner-read", "bucket-owner-full-control", or "private". If
463 we are not uploading to Google Storage, this parameter is unused.
464 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800465 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
466 uploading duplicate files, we just link to the old files.
467 """
468 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800469 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800470 self._binhost_base_url = binhost_base_url
471 self._pkg_indexes = pkg_indexes
David James8c846492011-01-25 17:07:29 -0800472
David Jamesc0f158a2011-02-22 16:07:29 -0800473 def _UploadPrebuilt(self, package_path, url_suffix):
474 """Upload host or board prebuilt files to Google Storage space.
David James8c846492011-01-25 17:07:29 -0800475
David Jamesc0f158a2011-02-22 16:07:29 -0800476 Args:
477 package_path: The path to the packages dir.
David Jamesce093af2011-02-23 15:21:58 -0800478 url_suffix: The remote subdirectory where we should upload the packages.
David Jamesc0f158a2011-02-22 16:07:29 -0800479 """
David James8c846492011-01-25 17:07:29 -0800480
David Jamesc0f158a2011-02-22 16:07:29 -0800481 # Process Packages file, removing duplicates and filtered packages.
482 pkg_index = GrabLocalPackageIndex(package_path)
483 pkg_index.SetUploadLocation(self._binhost_base_url, url_suffix)
484 pkg_index.RemoveFilteredPackages(ShouldFilterPackage)
485 uploads = pkg_index.ResolveDuplicateUploads(self._pkg_indexes)
David James05bcb2b2011-02-09 09:25:47 -0800486
David Jamesc0f158a2011-02-22 16:07:29 -0800487 # Write Packages file.
488 tmp_packages_file = pkg_index.WriteToNamedTemporaryFile()
David James05bcb2b2011-02-09 09:25:47 -0800489
David Jamesc0f158a2011-02-22 16:07:29 -0800490 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
491 if remote_location.startswith('gs://'):
492 # Build list of files to upload.
493 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
494 remote_file = '%s/Packages' % remote_location.rstrip('/')
495 upload_files[tmp_packages_file.name] = remote_file
David James05bcb2b2011-02-09 09:25:47 -0800496
David Jamesfd0b0852011-02-23 11:15:36 -0800497 failed_uploads = RemoteUpload(self._acl, upload_files)
David Jamesc0f158a2011-02-22 16:07:29 -0800498 if len(failed_uploads) > 1 or (None not in failed_uploads):
David Jamesf4db1122011-03-17 16:18:05 -0700499 error_msg = ['%s -> %s\n' % args for args in failed_uploads if args]
David Jamesc0f158a2011-02-22 16:07:29 -0800500 raise UploadFailed('Error uploading:\n%s' % error_msg)
501 else:
502 pkgs = ' '.join(p['CPV'] + '.tbz2' for p in uploads)
503 ssh_server, remote_path = remote_location.split(':', 1)
504 d = { 'pkg_index': tmp_packages_file.name,
505 'pkgs': pkgs,
506 'remote_packages': '%s/Packages' % remote_location.rstrip('/'),
507 'remote_path': remote_path.rstrip('/'),
508 'remote_location': remote_location.rstrip('/'),
509 'ssh_server': ssh_server }
510 cmds = ['ssh %(ssh_server)s mkdir -p %(remote_path)s' % d,
511 'rsync -av --chmod=a+r %(pkg_index)s %(remote_packages)s' % d]
512 if pkgs:
513 cmds.append('rsync -Rav %(pkgs)s %(remote_location)s/' % d)
514 for cmd in cmds:
515 if not _RetryRun(cmd, shell=True, cwd=package_path):
516 raise UploadFailed('Could not run %s' % cmd)
David James8c846492011-01-25 17:07:29 -0800517
David James8fa34ea2011-04-15 13:00:20 -0700518 def _UploadBoardTarball(self, board_path, url_suffix):
519 """Upload a tarball of the board at the specified path to Google Storage.
520
521 Args:
522 board_path: The path to the board dir.
523 url_suffix: The remote subdirectory where we should upload the packages.
524 """
525 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
526 assert remote_location.startswith('gs://')
527 cwd, boardname = os.path.split(board_path.rstrip(os.path.sep))
528 tmpdir = tempfile.mkdtemp()
529 try:
530 tarfile = os.path.join(tmpdir, '%s.tbz2' % boardname)
531 cmd = ['sudo', 'tar', '-I', 'pbzip2', '-cf', tarfile]
532 excluded_paths = ('usr/lib/debug', 'usr/local/autotest', 'packages',
533 'tmp')
534 for path in excluded_paths:
535 cmd.append('--exclude=%s/%s/*' % (boardname, path))
536 cmd.append(boardname)
537 cros_build_lib.RunCommand(cmd, cwd=cwd)
538 remote_tarfile = '%s/%s.tbz2' % (remote_location.rstrip('/'), boardname)
539 if _GsUpload((tarfile, remote_tarfile, self._acl)):
540 sys.exit(1)
541 finally:
542 cros_build_lib.RunCommand(['sudo', 'rm', '-rf', tmpdir], cwd=cwd)
543
David Jamesc0f158a2011-02-22 16:07:29 -0800544 def _SyncHostPrebuilts(self, build_path, version, key, git_sync,
545 sync_binhost_conf):
546 """Synchronize host prebuilt files.
David James05bcb2b2011-02-09 09:25:47 -0800547
David Jamesc0f158a2011-02-22 16:07:29 -0800548 This function will sync both the standard host packages, plus the host
549 packages associated with all targets that have been "setup" with the
550 current host's chroot. For instance, if this host has been used to build
551 x86-generic, it will sync the host packages associated with
552 'i686-pc-linux-gnu'. If this host has also been used to build arm-generic,
553 it will also sync the host packages associated with
554 'armv7a-cros-linux-gnueabi'.
David James05bcb2b2011-02-09 09:25:47 -0800555
David Jamesc0f158a2011-02-22 16:07:29 -0800556 Args:
557 build_path: The path to the directory containing the chroot.
558 version: A unique string, intended to be included in the upload path,
559 which identifies the version number of the uploaded prebuilts.
560 key: The variable key to update in the git file.
561 git_sync: If set, update make.conf of target to reference the latest
562 prebuilt packages generated here.
563 sync_binhost_conf: If set, update binhost config file in
564 chromiumos-overlay for the host.
565 """
566 # Upload prebuilts.
567 package_path = os.path.join(build_path, _HOST_PACKAGES_PATH)
568 url_suffix = _REL_HOST_PATH % {'version': version, 'target': _HOST_TARGET}
David James8fa34ea2011-04-15 13:00:20 -0700569 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
570 self._UploadPrebuilt(package_path, packages_url_suffix)
David James05bcb2b2011-02-09 09:25:47 -0800571
David Jamesc0f158a2011-02-22 16:07:29 -0800572 # Record URL where prebuilts were uploaded.
573 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
574 url_suffix.rstrip('/'))
575 if git_sync:
576 git_file = os.path.join(build_path, _PREBUILT_MAKE_CONF[_HOST_TARGET])
577 RevGitFile(git_file, url_value, key=key)
578 if sync_binhost_conf:
579 binhost_conf = os.path.join(build_path, _BINHOST_CONF_DIR, 'host',
580 '%s-%s.conf' % (_HOST_TARGET, key))
581 UpdateBinhostConfFile(binhost_conf, key, url_value)
582
583 def _SyncBoardPrebuilts(self, board, build_path, version, key, git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700584 sync_binhost_conf, upload_board_tarball):
David Jamesc0f158a2011-02-22 16:07:29 -0800585 """Synchronize board prebuilt files.
586
587 Args:
588 board: The board to upload to Google Storage.
589 build_path: The path to the directory containing the chroot.
590 version: A unique string, intended to be included in the upload path,
591 which identifies the version number of the uploaded prebuilts.
592 key: The variable key to update in the git file.
593 git_sync: If set, update make.conf of target to reference the latest
594 prebuilt packages generated here.
595 sync_binhost_conf: If set, update binhost config file in
596 chromiumos-overlay for the current board.
David James8fa34ea2011-04-15 13:00:20 -0700597 upload_board_tarball: Include a tarball of the board in our upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800598 """
David Jamesc0f158a2011-02-22 16:07:29 -0800599 board_path = os.path.join(build_path, _BOARD_PATH % {'board': board})
600 package_path = os.path.join(board_path, 'packages')
601 url_suffix = _REL_BOARD_PATH % {'board': board, 'version': version}
David James8fa34ea2011-04-15 13:00:20 -0700602 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
603
604 # Upload board tarballs in the background.
605 if upload_board_tarball:
606 tar_process = multiprocessing.Process(target=self._UploadBoardTarball,
607 args=(board_path, url_suffix))
608 tar_process.start()
609
610 # Upload prebuilts.
611 self._UploadPrebuilt(package_path, packages_url_suffix)
612
613 # Make sure we finished uploading the board tarballs.
614 if upload_board_tarball:
615 tar_process.join()
616 assert tar_process.exitcode == 0
David Jamesc0f158a2011-02-22 16:07:29 -0800617
618 # Record URL where prebuilts were uploaded.
619 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
620 url_suffix.rstrip('/'))
621 if git_sync:
622 git_file = DeterminePrebuiltConfFile(build_path, board)
623 RevGitFile(git_file, url_value, key=key)
624 if sync_binhost_conf:
625 binhost_conf = os.path.join(build_path, _BINHOST_CONF_DIR, 'target',
626 '%s-%s.conf' % (board, key))
627 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800628
629
David James8c846492011-01-25 17:07:29 -0800630def usage(parser, msg):
631 """Display usage message and parser help then exit with 1."""
632 print >> sys.stderr, msg
633 parser.print_help()
634 sys.exit(1)
635
David Jamesc0f158a2011-02-22 16:07:29 -0800636def ParseOptions():
David James8c846492011-01-25 17:07:29 -0800637 parser = optparse.OptionParser()
638 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
639 default=_BINHOST_BASE_URL,
640 help='Base URL to use for binhost in make.conf updates')
641 parser.add_option('', '--previous-binhost-url', action='append',
642 default=[], dest='previous_binhost_url',
643 help='Previous binhost URL')
644 parser.add_option('-b', '--board', dest='board', default=None,
645 help='Board type that was built on this machine')
646 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800647 help='Path to the directory containing the chroot')
David James8c846492011-01-25 17:07:29 -0800648 parser.add_option('-s', '--sync-host', dest='sync_host',
649 default=False, action='store_true',
650 help='Sync host prebuilts')
651 parser.add_option('-g', '--git-sync', dest='git_sync',
652 default=False, action='store_true',
653 help='Enable git version sync (This commits to a repo)')
654 parser.add_option('-u', '--upload', dest='upload',
655 default=None,
656 help='Upload location')
657 parser.add_option('-V', '--prepend-version', dest='prepend_version',
658 default=None,
659 help='Add an identifier to the front of the version')
660 parser.add_option('-f', '--filters', dest='filters', action='store_true',
661 default=False,
662 help='Turn on filtering of private ebuild packages')
663 parser.add_option('-k', '--key', dest='key',
664 default='PORTAGE_BINHOST',
665 help='Key to update in make.conf / binhost.conf')
666 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
667 default=False, action='store_true',
668 help='Update binhost.conf')
David Jamesfd0b0852011-02-23 11:15:36 -0800669 parser.add_option('-P', '--private', dest='private', action='store_true',
670 default=False, help='Mark gs:// uploads as private.')
David James8fa34ea2011-04-15 13:00:20 -0700671 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
672 action='store_true', default=False,
673 help='Upload board tarball to Google Storage.')
David James8c846492011-01-25 17:07:29 -0800674
675 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800676 if not options.build_path:
677 usage(parser, 'Error: you need provide a chroot path')
David James8c846492011-01-25 17:07:29 -0800678 if not options.upload:
679 usage(parser, 'Error: you need to provide an upload location using -u')
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700680
David James8fa34ea2011-04-15 13:00:20 -0700681
682 if options.upload_board_tarball and not options.upload.startswith('gs://'):
683 usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
684 '--upload must be a gs:// URL.')
685
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700686 if options.private:
687 if options.sync_host:
688 usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
689 'together, we do not support private host prebuilts')
690
691 if not options.upload.startswith('gs://'):
692 usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
693 '--upload must be a gs:// URL.')
694
695 if options.binhost_base_url != _BINHOST_BASE_URL:
696 usage(parser, 'Error: when using --private the --binhost-base-url '
697 'is automatically derived.')
David Jamesc0f158a2011-02-22 16:07:29 -0800698 return options
699
700def main():
701 options = ParseOptions()
702
David James8c846492011-01-25 17:07:29 -0800703 if options.filters:
704 LoadPrivateFilters(options.build_path)
705
David Jamesfd0b0852011-02-23 11:15:36 -0800706
David James05bcb2b2011-02-09 09:25:47 -0800707 # Calculate a list of Packages index files to compare against. Whenever we
708 # upload a package, we check to make sure it's not already stored in one of
709 # the packages files we uploaded. This list of packages files might contain
710 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800711 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800712
David Jamesc0f158a2011-02-22 16:07:29 -0800713 version = GetVersion()
714 if options.prepend_version:
715 version = '%s-%s' % (options.prepend_version, version)
716
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700717 acl = 'public-read'
718 binhost_base_url = options.binhost_base_url
719
720 if options.private:
721 binhost_base_url = options.upload
722 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
723 options.board)
724 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
725
726 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David Jamesc0f158a2011-02-22 16:07:29 -0800727 pkg_indexes)
728
David James8c846492011-01-25 17:07:29 -0800729 if options.sync_host:
David Jamesc0f158a2011-02-22 16:07:29 -0800730 uploader._SyncHostPrebuilts(options.build_path, version, options.key,
731 options.git_sync, options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800732
733 if options.board:
David Jamesc0f158a2011-02-22 16:07:29 -0800734 uploader._SyncBoardPrebuilts(options.board, options.build_path, version,
735 options.key, options.git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700736 options.sync_binhost_conf,
737 options.upload_board_tarball)
David James8c846492011-01-25 17:07:29 -0800738
739if __name__ == '__main__':
740 main()