blob: 9a2e74d2ace45e5ddac7033bee7c0af4215cff2c [file] [log] [blame]
David James8c846492011-01-25 17:07:29 -08001#!/usr/bin/python
David Jamesb619a782012-07-25 19:37:57 -07002# Copyright (c) 2012 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
Brian Harringaf019fb2012-05-10 15:06:13 -07006"""This script is used to upload host prebuilts as well as board BINHOSTS.
David James8c846492011-01-25 17:07:29 -08007
David James015af872012-06-19 15:24:36 -07008Prebuilts are uploaded using gsutil to Google Storage. After these prebuilts
9are successfully uploaded, a file is updated with the proper BINHOST version.
David James8c846492011-01-25 17:07:29 -080010
11To read more about prebuilts/binhost binary packages please refer to:
David James015af872012-06-19 15:24:36 -070012http://goto/chromeos-prebuilts
David James8c846492011-01-25 17:07:29 -080013
14Example of uploading prebuilt amd64 host files to Google Storage:
David Jamesc5cbd472012-06-19 16:25:45 -070015upload_prebuilts -p /b/cbuild/build -s -u gs://chromeos-prebuilt
David James8c846492011-01-25 17:07:29 -080016
17Example of uploading x86-dogfood binhosts to Google Storage:
David Jamesc5cbd472012-06-19 16:25:45 -070018upload_prebuilts -b x86-dogfood -p /b/cbuild/build/ -u gs://chromeos-prebuilt -g
David James8c846492011-01-25 17:07:29 -080019
David James8c846492011-01-25 17:07:29 -080020"""
21
Chris Sosa1dc96132012-05-11 15:40:50 -070022import datetime
23import multiprocessing
24import optparse
25import os
26import sys
27import tempfile
28
David James015af872012-06-19 15:24:36 -070029from chromite.buildbot import cbuildbot_background as bg
Chris Sosa1dc96132012-05-11 15:40:50 -070030from chromite.lib import cros_build_lib
Brian Harring7904b482012-08-08 02:54:12 -070031from chromite.lib import gs
Brian Harringaf019fb2012-05-10 15:06:13 -070032from chromite.lib import osutils
David James32faafe2012-06-08 14:25:03 -070033from chromite.lib import binpkg
Chris Sosa1dc96132012-05-11 15:40:50 -070034
David James015af872012-06-19 15:24:36 -070035# How many times to retry uploads.
36_RETRIES = 10
37
38# Multiplier for how long to sleep (in seconds) between retries; will delay
39# (1*sleep) the first time, then (2*sleep), continuing via attempt * sleep.
40_SLEEP_TIME = 60
41
David James8c846492011-01-25 17:07:29 -080042_HOST_PACKAGES_PATH = 'chroot/var/lib/portage/pkgs'
David James05bcb2b2011-02-09 09:25:47 -080043_CATEGORIES_PATH = 'chroot/etc/portage/categories'
David James615e5b52011-06-03 11:10:15 -070044_PYM_PATH = 'chroot/usr/lib/portage/pym'
David James4058b0d2011-12-08 21:24:50 -080045_HOST_ARCH = 'amd64'
David James8c846492011-01-25 17:07:29 -080046_BOARD_PATH = 'chroot/build/%(board)s'
David James4058b0d2011-12-08 21:24:50 -080047_REL_BOARD_PATH = 'board/%(target)s/%(version)s'
48_REL_HOST_PATH = 'host/%(host_arch)s/%(target)s/%(version)s'
David James8c846492011-01-25 17:07:29 -080049# Private overlays to look at for builds to filter
50# relative to build path
51_PRIVATE_OVERLAY_DIR = 'src/private-overlays'
Scott Zawalskiab1bed32011-03-16 15:24:24 -070052_GOOGLESTORAGE_ACL_FILE = 'googlestorage_acl.xml'
David Jamesce619292011-11-08 11:42:36 -080053_BINHOST_BASE_URL = 'https://commondatastorage.googleapis.com/chromeos-prebuilt'
David James8c846492011-01-25 17:07:29 -080054_PREBUILT_BASE_DIR = 'src/third_party/chromiumos-overlay/chromeos/config/'
55# Created in the event of new host targets becoming available
56_PREBUILT_MAKE_CONF = {'amd64': os.path.join(_PREBUILT_BASE_DIR,
57 'make.conf.amd64-host')}
58_BINHOST_CONF_DIR = 'src/third_party/chromiumos-overlay/chromeos/binhost'
59
60
David James4058b0d2011-12-08 21:24:50 -080061class BuildTarget(object):
62 """A board/variant/profile tuple."""
63
64 def __init__(self, board_variant, profile=None):
65 self.board_variant = board_variant
66 self.board, _, self.variant = board_variant.partition('_')
67 self.profile = profile
68
69 def __str__(self):
70 if self.profile:
71 return '%s_%s' % (self.board_variant, self.profile)
72 else:
73 return self.board_variant
74
75 def __eq__(self, other):
76 return str(other) == str(self)
77
78 def __hash__(self):
79 return hash(str(self))
80
81
David James8c846492011-01-25 17:07:29 -080082def UpdateLocalFile(filename, value, key='PORTAGE_BINHOST'):
83 """Update the key in file with the value passed.
84 File format:
85 key="value"
86 Note quotes are added automatically
87
88 Args:
89 filename: Name of file to modify.
90 value: Value to write with the key.
91 key: The variable key to update. (Default: PORTAGE_BINHOST)
92 """
93 if os.path.exists(filename):
94 file_fh = open(filename)
95 else:
96 file_fh = open(filename, 'w+')
97 file_lines = []
98 found = False
99 keyval_str = '%(key)s=%(value)s'
100 for line in file_fh:
101 # Strip newlines from end of line. We already add newlines below.
102 line = line.rstrip("\n")
103
104 if len(line.split('=')) != 2:
105 # Skip any line that doesn't fit key=val.
106 file_lines.append(line)
107 continue
108
109 file_var, file_val = line.split('=')
110 if file_var == key:
111 found = True
David James20b2b6f2011-11-18 15:11:58 -0800112 print 'Updating %s=%s to %s="%s"' % (file_var, file_val, key, value)
David James8c846492011-01-25 17:07:29 -0800113 value = '"%s"' % value
114 file_lines.append(keyval_str % {'key': key, 'value': value})
115 else:
116 file_lines.append(keyval_str % {'key': file_var, 'value': file_val})
117
118 if not found:
Brian Harring2a014302012-05-12 00:53:33 -0700119 value = '"%s"' % value
David James8c846492011-01-25 17:07:29 -0800120 file_lines.append(keyval_str % {'key': key, 'value': value})
121
122 file_fh.close()
123 # write out new file
Brian Harringaf019fb2012-05-10 15:06:13 -0700124 osutils.WriteFile(filename, '\n'.join(file_lines) + '\n')
David James8c846492011-01-25 17:07:29 -0800125
126
David James27fa7d12011-06-29 17:24:14 -0700127def RevGitFile(filename, value, retries=5, key='PORTAGE_BINHOST', dryrun=False):
David James8c846492011-01-25 17:07:29 -0800128 """Update and push the git file.
129
130 Args:
131 filename: file to modify that is in a git repo already
132 value: string representing the version of the prebuilt that has been
133 uploaded.
134 retries: The number of times to retry before giving up, default: 5
135 key: The variable key to update in the git file.
136 (Default: PORTAGE_BINHOST)
137 """
138 prebuilt_branch = 'prebuilt_branch'
David James1b6e67a2011-05-19 21:32:38 -0700139 cwd = os.path.abspath(os.path.dirname(filename))
Brian Harring609dc4e2012-05-07 02:17:44 -0700140 commit = cros_build_lib.RunGitCommand(
141 cwd, ['rev-parse', 'HEAD']).output.rstrip()
David James8c846492011-01-25 17:07:29 -0800142 description = 'Update %s="%s" in %s' % (key, value, filename)
143 print description
David James66009462012-03-25 10:08:38 -0700144
David James8c846492011-01-25 17:07:29 -0800145 try:
David James66009462012-03-25 10:08:38 -0700146 cros_build_lib.CreatePushBranch(prebuilt_branch, cwd)
David James8c846492011-01-25 17:07:29 -0800147 UpdateLocalFile(filename, value, key)
Brian Harring609dc4e2012-05-07 02:17:44 -0700148 cros_build_lib.RunGitCommand(cwd, ['add', filename])
149 cros_build_lib.RunGitCommand(cwd, ['commit', '-m', description])
150 cros_build_lib.GitPushWithRetry(prebuilt_branch, cwd, dryrun=dryrun,
David James66009462012-03-25 10:08:38 -0700151 retries=retries)
David James8c846492011-01-25 17:07:29 -0800152 finally:
David James1b6e67a2011-05-19 21:32:38 -0700153 cros_build_lib.RunCommand(['git', 'checkout', commit], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800154
155
156def GetVersion():
157 """Get the version to put in LATEST and update the git version with."""
158 return datetime.datetime.now().strftime('%d.%m.%y.%H%M%S')
159
160
David James015af872012-06-19 15:24:36 -0700161def _GsUpload(local_file, remote_file, acl):
David James8c846492011-01-25 17:07:29 -0800162 """Upload to GS bucket.
163
164 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800165 args: a tuple of three arguments that contains local_file, remote_file, and
166 the acl used for uploading the file.
David James8c846492011-01-25 17:07:29 -0800167
168 Returns:
169 Return the arg tuple of two if the upload failed
170 """
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700171 CANNED_ACLS = ['public-read', 'private', 'bucket-owner-read',
172 'authenticated-read', 'bucket-owner-full-control',
173 'public-read-write']
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700174 if acl in CANNED_ACLS:
Brian Harring7904b482012-08-08 02:54:12 -0700175 cmd = [gs.GSUTIL_BIN, 'cp', '-a', acl, local_file, remote_file]
David James015af872012-06-19 15:24:36 -0700176 acl_cmd = None
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700177 else:
178 # For private uploads we assume that the overlay board is set up properly
David James015af872012-06-19 15:24:36 -0700179 # and a googlestore_acl.xml is present. Otherwise, this script errors.
Brian Harring7904b482012-08-08 02:54:12 -0700180 cmd = [gs.GSUTIL_BIN, 'cp', '-a', 'private', local_file, remote_file]
181 acl_cmd = [gs.GSUTIL_BIN, 'setacl', acl, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700182
David Jamesc5cbd472012-06-19 16:25:45 -0700183 cros_build_lib.RunCommandWithRetries(_RETRIES, cmd, print_cmd=True,
184 sleep=_SLEEP_TIME,
185 redirect_stdout=True,
186 redirect_stderr=True)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700187 if acl_cmd:
188 # Apply the passed in ACL xml file to the uploaded object.
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700189 cros_build_lib.RunCommandWithRetries(_RETRIES, acl_cmd, print_cmd=False,
David James015af872012-06-19 15:24:36 -0700190 sleep=_SLEEP_TIME)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700191
192
David Jamesfd0b0852011-02-23 11:15:36 -0800193def RemoteUpload(acl, files, pool=10):
David James8c846492011-01-25 17:07:29 -0800194 """Upload to google storage.
195
196 Create a pool of process and call _GsUpload with the proper arguments.
197
198 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800199 acl: The canned acl used for uploading. acl can be one of: "public-read",
200 "public-read-write", "authenticated-read", "bucket-owner-read",
201 "bucket-owner-full-control", or "private".
David James8c846492011-01-25 17:07:29 -0800202 files: dictionary with keys to local files and values to remote path.
203 pool: integer of maximum proesses to have at the same time.
204
205 Returns:
206 Return a set of tuple arguments of the failed uploads
207 """
David James015af872012-06-19 15:24:36 -0700208 tasks = [[key, value, acl] for key, value in files.iteritems()]
209 bg.RunTasksInProcessPool(_GsUpload, tasks, pool)
David James8c846492011-01-25 17:07:29 -0800210
211
212def GenerateUploadDict(base_local_path, base_remote_path, pkgs):
213 """Build a dictionary of local remote file key pairs to upload.
214
215 Args:
216 base_local_path: The base path to the files on the local hard drive.
217 remote_path: The base path to the remote paths.
218 pkgs: The packages to upload.
219
220 Returns:
221 Returns a dictionary of local_path/remote_path pairs
222 """
223 upload_files = {}
224 for pkg in pkgs:
225 suffix = pkg['CPV'] + '.tbz2'
226 local_path = os.path.join(base_local_path, suffix)
227 assert os.path.exists(local_path)
228 remote_path = '%s/%s' % (base_remote_path.rstrip('/'), suffix)
229 upload_files[local_path] = remote_path
230
231 return upload_files
232
233def GetBoardPathFromCrosOverlayList(build_path, target):
234 """Use the cros_overlay_list to determine the path to the board overlay
235 Args:
236 build_path: The path to the root of the build directory
237 target: The target that we are looking for, could consist of board and
238 board_variant, we handle that properly
239 Returns:
240 The last line from cros_overlay_list as a string
241 """
Chris Sosa471532a2011-02-01 15:10:06 -0800242 script_dir = os.path.join(build_path, 'src/platform/dev/host')
David James4058b0d2011-12-08 21:24:50 -0800243 cmd = ['./cros_overlay_list', '--board', target.board]
244 if target.variant:
245 cmd += ['--variant', target.variant]
David James8c846492011-01-25 17:07:29 -0800246
247 cmd_output = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
248 cwd=script_dir)
249 # We only care about the last entry
250 return cmd_output.output.splitlines().pop()
251
252
253def DeterminePrebuiltConfFile(build_path, target):
254 """Determine the prebuilt.conf file that needs to be updated for prebuilts.
255
256 Args:
257 build_path: The path to the root of the build directory
258 target: String representation of the board. This includes host and board
259 targets
260
261 Returns
262 A string path to a prebuilt.conf file to be updated.
263 """
David James4058b0d2011-12-08 21:24:50 -0800264 if _HOST_ARCH == target:
David James8c846492011-01-25 17:07:29 -0800265 # We are host.
266 # Without more examples of hosts this is a kludge for now.
267 # TODO(Scottz): as new host targets come online expand this to
268 # work more like boards.
Chris Sosa471532a2011-02-01 15:10:06 -0800269 make_path = _PREBUILT_MAKE_CONF[target]
David James8c846492011-01-25 17:07:29 -0800270 else:
271 # We are a board
272 board = GetBoardPathFromCrosOverlayList(build_path, target)
273 make_path = os.path.join(board, 'prebuilt.conf')
274
275 return make_path
276
277
278def UpdateBinhostConfFile(path, key, value):
279 """Update binhost config file file with key=value.
280
281 Args:
282 path: Filename to update.
283 key: Key to update.
284 value: New value for key.
285 """
286 cwd = os.path.dirname(os.path.abspath(path))
287 filename = os.path.basename(path)
Brian Harringaf019fb2012-05-10 15:06:13 -0700288 osutils.SafeMakedirs(cwd)
Brian Harring22edb442012-05-11 23:55:18 -0700289 osutils.WriteFile(path, '', mode='a')
David James8c846492011-01-25 17:07:29 -0800290 UpdateLocalFile(path, value, key)
Chris Sosac13bba52011-05-24 15:14:09 -0700291 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
David James20b2b6f2011-11-18 15:11:58 -0800292 description = 'Update %s="%s" in %s' % (key, value, filename)
Peter Mayo193f68f2011-04-19 19:08:21 -0400293 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800294
295
David Jamesce093af2011-02-23 15:21:58 -0800296def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800297 """Grab all of the packages files associated with a list of binhost_urls.
298
David James05bcb2b2011-02-09 09:25:47 -0800299 Args:
300 binhost_urls: The URLs for the directories containing the Packages files we
301 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800302
303 Returns:
304 A list of PackageIndex objects.
305 """
306 pkg_indexes = []
307 for url in binhost_urls:
David James32faafe2012-06-08 14:25:03 -0700308 pkg_index = binpkg.GrabRemotePackageIndex(url)
David James05bcb2b2011-02-09 09:25:47 -0800309 if pkg_index:
310 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800311 return pkg_indexes
312
313
David James05bcb2b2011-02-09 09:25:47 -0800314
David Jamesc0f158a2011-02-22 16:07:29 -0800315class PrebuiltUploader(object):
316 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800317
David James615e5b52011-06-03 11:10:15 -0700318 def __init__(self, upload_location, acl, binhost_base_url,
David James32b0b2f2011-07-13 20:56:50 -0700319 pkg_indexes, build_path, packages, skip_upload,
David James4058b0d2011-12-08 21:24:50 -0800320 binhost_conf_dir, debug, target, slave_targets):
David Jamesc0f158a2011-02-22 16:07:29 -0800321 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800322
David Jamesc0f158a2011-02-22 16:07:29 -0800323 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800324
David Jamesc0f158a2011-02-22 16:07:29 -0800325 Args:
326 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800327 acl: The canned acl used for uploading to Google Storage. acl can be one
328 of: "public-read", "public-read-write", "authenticated-read",
329 "bucket-owner-read", "bucket-owner-full-control", or "private". If
330 we are not uploading to Google Storage, this parameter is unused.
331 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800332 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
333 uploading duplicate files, we just link to the old files.
David James615e5b52011-06-03 11:10:15 -0700334 build_path: The path to the directory containing the chroot.
335 packages: Packages to upload.
David James32b0b2f2011-07-13 20:56:50 -0700336 skip_upload: Don't actually upload the tarballs.
337 binhost_conf_dir: Directory where to store binhost.conf files.
338 debug: Don't push or upload prebuilts.
David James4058b0d2011-12-08 21:24:50 -0800339 target: BuildTarget managed by this builder.
340 slave_targets: List of BuildTargets managed by slave builders.
David Jamesc0f158a2011-02-22 16:07:29 -0800341 """
342 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800343 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800344 self._binhost_base_url = binhost_base_url
345 self._pkg_indexes = pkg_indexes
David James615e5b52011-06-03 11:10:15 -0700346 self._build_path = build_path
347 self._packages = set(packages)
David James8ece7ee2011-06-29 16:02:30 -0700348 self._skip_upload = skip_upload
David James32b0b2f2011-07-13 20:56:50 -0700349 self._binhost_conf_dir = binhost_conf_dir
David James27fa7d12011-06-29 17:24:14 -0700350 self._debug = debug
David James4058b0d2011-12-08 21:24:50 -0800351 self._target = target
352 self._slave_targets = slave_targets
David James615e5b52011-06-03 11:10:15 -0700353
354 def _ShouldFilterPackage(self, pkg):
355 if not self._packages:
356 return False
357 pym_path = os.path.abspath(os.path.join(self._build_path, _PYM_PATH))
David James710b7dc2012-02-07 16:49:59 -0800358 sys.path.insert(0, pym_path)
David James615e5b52011-06-03 11:10:15 -0700359 import portage.versions
360 cat, pkgname = portage.versions.catpkgsplit(pkg['CPV'])[0:2]
361 cp = '%s/%s' % (cat, pkgname)
362 return pkgname not in self._packages and cp not in self._packages
David James8c846492011-01-25 17:07:29 -0800363
David Jamesc0f158a2011-02-22 16:07:29 -0800364 def _UploadPrebuilt(self, package_path, url_suffix):
365 """Upload host or board prebuilt files to Google Storage space.
David James8c846492011-01-25 17:07:29 -0800366
David Jamesc0f158a2011-02-22 16:07:29 -0800367 Args:
368 package_path: The path to the packages dir.
David Jamesce093af2011-02-23 15:21:58 -0800369 url_suffix: The remote subdirectory where we should upload the packages.
David Jamesa3bba142011-05-26 21:24:20 -0700370
David Jamesc0f158a2011-02-22 16:07:29 -0800371 """
David James8c846492011-01-25 17:07:29 -0800372
David Jamesc0f158a2011-02-22 16:07:29 -0800373 # Process Packages file, removing duplicates and filtered packages.
David James32faafe2012-06-08 14:25:03 -0700374 pkg_index = binpkg.GrabLocalPackageIndex(package_path)
David Jamesc0f158a2011-02-22 16:07:29 -0800375 pkg_index.SetUploadLocation(self._binhost_base_url, url_suffix)
David James615e5b52011-06-03 11:10:15 -0700376 pkg_index.RemoveFilteredPackages(self._ShouldFilterPackage)
David Jamesc0f158a2011-02-22 16:07:29 -0800377 uploads = pkg_index.ResolveDuplicateUploads(self._pkg_indexes)
David James05bcb2b2011-02-09 09:25:47 -0800378
David Jamesc0f158a2011-02-22 16:07:29 -0800379 # Write Packages file.
380 tmp_packages_file = pkg_index.WriteToNamedTemporaryFile()
David James05bcb2b2011-02-09 09:25:47 -0800381
David Jamesc0f158a2011-02-22 16:07:29 -0800382 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
David James015af872012-06-19 15:24:36 -0700383 assert remote_location.startswith('gs://')
David James05bcb2b2011-02-09 09:25:47 -0800384
David James015af872012-06-19 15:24:36 -0700385 # Build list of files to upload.
386 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
387 remote_file = '%s/Packages' % remote_location.rstrip('/')
388 upload_files[tmp_packages_file.name] = remote_file
389
390 RemoteUpload(self._acl, upload_files)
David James8c846492011-01-25 17:07:29 -0800391
Zdenek Behan62a57792012-08-31 15:09:08 +0200392 def _UploadBoardTarball(self, board_path, url_suffix, version, prepackaged):
David James8fa34ea2011-04-15 13:00:20 -0700393 """Upload a tarball of the board at the specified path to Google Storage.
394
395 Args:
396 board_path: The path to the board dir.
397 url_suffix: The remote subdirectory where we should upload the packages.
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200398 version: The version of the board.
Zdenek Behan62a57792012-08-31 15:09:08 +0200399 prepackaged: If given, a tarball that has been packaged outside of this
400 script and should be used.
David James8fa34ea2011-04-15 13:00:20 -0700401 """
402 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
403 assert remote_location.startswith('gs://')
404 cwd, boardname = os.path.split(board_path.rstrip(os.path.sep))
David James8fa34ea2011-04-15 13:00:20 -0700405 try:
Zdenek Behan62a57792012-08-31 15:09:08 +0200406 tmpdir = tempfile.mkdtemp()
407 if prepackaged is None:
408 tarfile = os.path.join(tmpdir, '%s.tbz2' % boardname)
409 bzip2 = cros_build_lib.FindCompressor(cros_build_lib.COMP_BZIP2)
410 cmd = ['tar', '-I', bzip2, '-cf', tarfile]
411 excluded_paths = ('usr/lib/debug', 'usr/local/autotest', 'packages',
412 'tmp')
413 for path in excluded_paths:
414 cmd.append('--exclude=%s/*' % path)
415 cmd.append('.')
416 cros_build_lib.SudoRunCommand(cmd, cwd=os.path.join(cwd, boardname))
417 else:
418 tarfile = prepackaged
419
David James8fa34ea2011-04-15 13:00:20 -0700420 remote_tarfile = '%s/%s.tbz2' % (remote_location.rstrip('/'), boardname)
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200421 # FIXME(zbehan): Temporary hack to upload amd64-host chroots to a
422 # different gs bucket. The right way is to do the upload in a separate
423 # pass of this script.
424 if boardname == 'amd64-host':
Zdenek Behanaf3c9002011-06-24 10:07:40 +0200425 # FIXME(zbehan): Why does version contain the prefix "chroot-"?
426 remote_tarfile = \
427 'gs://chromiumos-sdk/cros-sdk-%s.tbz2' % version.strip('chroot-')
David James015af872012-06-19 15:24:36 -0700428 _GsUpload(tarfile, remote_tarfile, self._acl)
David James8fa34ea2011-04-15 13:00:20 -0700429 finally:
Brian Harringd223a242012-02-03 20:12:10 -0800430 cros_build_lib.SudoRunCommand(['rm', '-rf', tmpdir], cwd=cwd)
David James8fa34ea2011-04-15 13:00:20 -0700431
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700432 def _GetTargets(self):
433 """Retuns the list of targets to use."""
434 targets = self._slave_targets[:]
435 if self._target:
436 targets.append(self._target)
437
438 return targets
439
440 def SyncHostPrebuilts(self, version, key, git_sync, sync_binhost_conf):
David Jamesc0f158a2011-02-22 16:07:29 -0800441 """Synchronize host prebuilt files.
David James05bcb2b2011-02-09 09:25:47 -0800442
David Jamesc0f158a2011-02-22 16:07:29 -0800443 This function will sync both the standard host packages, plus the host
444 packages associated with all targets that have been "setup" with the
445 current host's chroot. For instance, if this host has been used to build
446 x86-generic, it will sync the host packages associated with
447 'i686-pc-linux-gnu'. If this host has also been used to build arm-generic,
448 it will also sync the host packages associated with
449 'armv7a-cros-linux-gnueabi'.
David James05bcb2b2011-02-09 09:25:47 -0800450
David Jamesc0f158a2011-02-22 16:07:29 -0800451 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800452 version: A unique string, intended to be included in the upload path,
453 which identifies the version number of the uploaded prebuilts.
454 key: The variable key to update in the git file.
455 git_sync: If set, update make.conf of target to reference the latest
456 prebuilt packages generated here.
457 sync_binhost_conf: If set, update binhost config file in
458 chromiumos-overlay for the host.
459 """
David Jamese2488642011-11-14 16:15:20 -0800460 # Slave boards are listed before the master board so that the master board
461 # takes priority (i.e. x86-generic preflight host prebuilts takes priority
462 # over preflight host prebuilts from other builders.)
463 binhost_urls = []
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700464 for target in self._GetTargets():
David James4058b0d2011-12-08 21:24:50 -0800465 url_suffix = _REL_HOST_PATH % {'version': version,
466 'host_arch': _HOST_ARCH,
467 'target': target}
David Jamese2488642011-11-14 16:15:20 -0800468 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James05bcb2b2011-02-09 09:25:47 -0800469
David James4058b0d2011-12-08 21:24:50 -0800470 if self._target == target and not self._skip_upload and not self._debug:
David Jamese2488642011-11-14 16:15:20 -0800471 # Upload prebuilts.
472 package_path = os.path.join(self._build_path, _HOST_PACKAGES_PATH)
473 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8ece7ee2011-06-29 16:02:30 -0700474
David Jamese2488642011-11-14 16:15:20 -0800475 # Record URL where prebuilts were uploaded.
476 binhost_urls.append('%s/%s/' % (self._binhost_base_url.rstrip('/'),
477 packages_url_suffix.rstrip('/')))
478
David James20b2b6f2011-11-18 15:11:58 -0800479 binhost = ' '.join(binhost_urls)
David James8ece7ee2011-06-29 16:02:30 -0700480 if git_sync:
481 git_file = os.path.join(self._build_path,
David James4058b0d2011-12-08 21:24:50 -0800482 _PREBUILT_MAKE_CONF[_HOST_ARCH])
David Jamese2488642011-11-14 16:15:20 -0800483 RevGitFile(git_file, binhost, key=key, dryrun=self._debug)
David James8ece7ee2011-06-29 16:02:30 -0700484 if sync_binhost_conf:
David James32b0b2f2011-07-13 20:56:50 -0700485 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James4058b0d2011-12-08 21:24:50 -0800486 'host', '%s-%s.conf' % (_HOST_ARCH, key))
David Jamese2488642011-11-14 16:15:20 -0800487 UpdateBinhostConfFile(binhost_conf, key, binhost)
David Jamesc0f158a2011-02-22 16:07:29 -0800488
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700489 def SyncBoardPrebuilts(self, version, key, git_sync, sync_binhost_conf,
Zdenek Behan62a57792012-08-31 15:09:08 +0200490 upload_board_tarball, prepackaged_board):
David Jamesc0f158a2011-02-22 16:07:29 -0800491 """Synchronize board prebuilt files.
492
493 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800494 version: A unique string, intended to be included in the upload path,
495 which identifies the version number of the uploaded prebuilts.
496 key: The variable key to update in the git file.
497 git_sync: If set, update make.conf of target to reference the latest
498 prebuilt packages generated here.
499 sync_binhost_conf: If set, update binhost config file in
500 chromiumos-overlay for the current board.
David James8fa34ea2011-04-15 13:00:20 -0700501 upload_board_tarball: Include a tarball of the board in our upload.
Zdenek Behan62a57792012-08-31 15:09:08 +0200502 prepackaged_board: A tarball of the board built outside of this script.
David Jamesc0f158a2011-02-22 16:07:29 -0800503 """
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700504 for target in self._GetTargets():
David Jamese2488642011-11-14 16:15:20 -0800505 board_path = os.path.join(self._build_path,
David James4058b0d2011-12-08 21:24:50 -0800506 _BOARD_PATH % {'board': target.board_variant})
David Jamese2488642011-11-14 16:15:20 -0800507 package_path = os.path.join(board_path, 'packages')
David James4058b0d2011-12-08 21:24:50 -0800508 url_suffix = _REL_BOARD_PATH % {'target': target, 'version': version}
David Jamese2488642011-11-14 16:15:20 -0800509 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James8fa34ea2011-04-15 13:00:20 -0700510
David James4058b0d2011-12-08 21:24:50 -0800511 if self._target == target and not self._skip_upload and not self._debug:
David Jamese2488642011-11-14 16:15:20 -0800512 # Upload board tarballs in the background.
513 if upload_board_tarball:
514 tar_process = multiprocessing.Process(target=self._UploadBoardTarball,
515 args=(board_path, url_suffix,
Zdenek Behan62a57792012-08-31 15:09:08 +0200516 version,
517 prepackaged_board))
David Jamese2488642011-11-14 16:15:20 -0800518 tar_process.start()
David James8fa34ea2011-04-15 13:00:20 -0700519
David Jamese2488642011-11-14 16:15:20 -0800520 # Upload prebuilts.
521 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8fa34ea2011-04-15 13:00:20 -0700522
David Jamese2488642011-11-14 16:15:20 -0800523 # Make sure we finished uploading the board tarballs.
524 if upload_board_tarball:
525 tar_process.join()
526 assert tar_process.exitcode == 0
527 # TODO(zbehan): This should be done cleaner.
David James4058b0d2011-12-08 21:24:50 -0800528 if target.board == 'amd64-host':
David Jamese2488642011-11-14 16:15:20 -0800529 sdk_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James8ece7ee2011-06-29 16:02:30 -0700530 'host/sdk_version.conf')
David Jamese2488642011-11-14 16:15:20 -0800531 RevGitFile(sdk_conf, version.strip('chroot-'),
532 key='SDK_LATEST_VERSION', dryrun=self._debug)
David Jamesc0f158a2011-02-22 16:07:29 -0800533
David Jamese2488642011-11-14 16:15:20 -0800534 # Record URL where prebuilts were uploaded.
535 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
536 packages_url_suffix.rstrip('/'))
537
538 if git_sync:
David James4058b0d2011-12-08 21:24:50 -0800539 git_file = DeterminePrebuiltConfFile(self._build_path, target)
David Jamese2488642011-11-14 16:15:20 -0800540 RevGitFile(git_file, url_value, key=key, dryrun=self._debug)
541 if sync_binhost_conf:
542 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James4058b0d2011-12-08 21:24:50 -0800543 'target', '%s-%s.conf' % (target, key))
David Jamese2488642011-11-14 16:15:20 -0800544 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800545
546
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700547def Usage(parser, msg):
David James8c846492011-01-25 17:07:29 -0800548 """Display usage message and parser help then exit with 1."""
549 print >> sys.stderr, msg
550 parser.print_help()
551 sys.exit(1)
552
David James4058b0d2011-12-08 21:24:50 -0800553
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700554def _AddSlaveBoard(_option, _opt_str, value, parser):
555 """Callback that adds a slave board to the list of slave targets."""
David James4058b0d2011-12-08 21:24:50 -0800556 parser.values.slave_targets.append(BuildTarget(value))
557
558
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700559def _AddSlaveProfile(_option, _opt_str, value, parser):
560 """Callback that adds a slave profile to the list of slave targets."""
David James4058b0d2011-12-08 21:24:50 -0800561 if not parser.values.slave_targets:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700562 Usage(parser, 'Must specify --slave-board before --slave-profile')
David James4058b0d2011-12-08 21:24:50 -0800563 if parser.values.slave_targets[-1].profile is not None:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700564 Usage(parser, 'Cannot specify --slave-profile twice for same board')
David James4058b0d2011-12-08 21:24:50 -0800565 parser.values.slave_targets[-1].profile = value
566
567
David Jamesc0f158a2011-02-22 16:07:29 -0800568def ParseOptions():
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700569 """Returns options given by the user and the target specified.
570
571 Returns a tuple containing a parsed options object and BuildTarget.
572 target instance is None if no board is specified.
573 """
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')
Zdenek Behan62a57792012-08-31 15:09:08 +0200583 parser.add_option('-B', '--prepackaged-tarball', dest='prepackaged_tarball',
584 default=None,
585 help='Board tarball prebuilt outside of this script.')
David James4058b0d2011-12-08 21:24:50 -0800586 parser.add_option('', '--profile', dest='profile', default=None,
587 help='Profile that was built on this machine')
588 parser.add_option('', '--slave-board', default=[], action='callback',
589 dest='slave_targets', type='string',
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700590 callback=_AddSlaveBoard,
David James4058b0d2011-12-08 21:24:50 -0800591 help='Board type that was built on a slave machine. To '
592 'add a profile to this board, use --slave-profile.')
593 parser.add_option('', '--slave-profile', action='callback', type='string',
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700594 callback=_AddSlaveProfile,
David James4058b0d2011-12-08 21:24:50 -0800595 help='Board profile that was built on a slave machine. '
596 'Applies to previous slave board.')
David James8c846492011-01-25 17:07:29 -0800597 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800598 help='Path to the directory containing the chroot')
David James615e5b52011-06-03 11:10:15 -0700599 parser.add_option('', '--packages', action='append',
600 default=[], dest='packages',
601 help='Only include the specified packages. '
602 '(Default is to include all packages.)')
David James8c846492011-01-25 17:07:29 -0800603 parser.add_option('-s', '--sync-host', dest='sync_host',
604 default=False, action='store_true',
605 help='Sync host prebuilts')
606 parser.add_option('-g', '--git-sync', dest='git_sync',
607 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800608 help='Enable git version sync (This commits to a repo.) '
609 'This is used by full builders to commit directly '
610 'to board overlays.')
David James8c846492011-01-25 17:07:29 -0800611 parser.add_option('-u', '--upload', dest='upload',
612 default=None,
613 help='Upload location')
614 parser.add_option('-V', '--prepend-version', dest='prepend_version',
615 default=None,
616 help='Add an identifier to the front of the version')
617 parser.add_option('-f', '--filters', dest='filters', action='store_true',
618 default=False,
619 help='Turn on filtering of private ebuild packages')
620 parser.add_option('-k', '--key', dest='key',
621 default='PORTAGE_BINHOST',
622 help='Key to update in make.conf / binhost.conf')
David James8ece7ee2011-06-29 16:02:30 -0700623 parser.add_option('', '--set-version', dest='set_version',
624 default=None,
625 help='Specify the version string')
David James8c846492011-01-25 17:07:29 -0800626 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
627 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800628 help='Update binhost.conf in chromiumos-overlay or '
629 'chromeos-overlay. Commit the changes, but don\'t '
630 'push them. This is used for preflight binhosts.')
David James32b0b2f2011-07-13 20:56:50 -0700631 parser.add_option('', '--binhost-conf-dir', dest='binhost_conf_dir',
632 default=_BINHOST_CONF_DIR,
633 help='Directory to commit binhost config with '
634 '--sync-binhost-conf.')
David Jamesfd0b0852011-02-23 11:15:36 -0800635 parser.add_option('-P', '--private', dest='private', action='store_true',
636 default=False, help='Mark gs:// uploads as private.')
David James8ece7ee2011-06-29 16:02:30 -0700637 parser.add_option('', '--skip-upload', dest='skip_upload',
638 action='store_true', default=False,
639 help='Skip upload step.')
David James8fa34ea2011-04-15 13:00:20 -0700640 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
641 action='store_true', default=False,
642 help='Upload board tarball to Google Storage.')
David James27fa7d12011-06-29 17:24:14 -0700643 parser.add_option('', '--debug', dest='debug',
644 action='store_true', default=False,
645 help='Don\'t push or upload prebuilts.')
David James8c846492011-01-25 17:07:29 -0800646
647 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800648 if not options.build_path:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700649 Usage(parser, 'Error: you need provide a chroot path')
David James8ece7ee2011-06-29 16:02:30 -0700650 if not options.upload and not options.skip_upload:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700651 Usage(parser, 'Error: you need to provide an upload location using -u')
David James8ece7ee2011-06-29 16:02:30 -0700652 if not options.set_version and options.skip_upload:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700653 Usage(parser, 'Error: If you are using --skip-upload, you must specify a '
David James8ece7ee2011-06-29 16:02:30 -0700654 'version number using --set-version.')
David James9417f272011-05-26 13:24:47 -0700655 if args:
David Jamesc5cbd472012-06-19 16:25:45 -0700656 Usage(parser, 'Error: invalid arguments passed to upload_prebuilts: '
657 '%r' % args)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700658
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700659 target = None
660 if options.board:
661 target = BuildTarget(options.board, options.profile)
662
663 if target in options.slave_targets:
664 Usage(parser, 'Error: --board/--profile must not also be a slave target.')
David Jamese2488642011-11-14 16:15:20 -0800665
David James4058b0d2011-12-08 21:24:50 -0800666 if len(set(options.slave_targets)) != len(options.slave_targets):
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700667 Usage(parser, 'Error: --slave-boards must not have duplicates.')
David Jamese2488642011-11-14 16:15:20 -0800668
David James4058b0d2011-12-08 21:24:50 -0800669 if options.slave_targets and options.git_sync:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700670 Usage(parser, 'Error: --slave-boards is not compatible with --git-sync')
David Jamese2488642011-11-14 16:15:20 -0800671
David James8ece7ee2011-06-29 16:02:30 -0700672 if (options.upload_board_tarball and options.skip_upload and
673 options.board == 'amd64-host'):
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700674 Usage(parser, 'Error: --skip-upload is not compatible with '
David James8ece7ee2011-06-29 16:02:30 -0700675 '--upload-board-tarball and --board=amd64-host')
David James8fa34ea2011-04-15 13:00:20 -0700676
David James8ece7ee2011-06-29 16:02:30 -0700677 if (options.upload_board_tarball and not options.skip_upload and
678 not options.upload.startswith('gs://')):
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700679 Usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
David James8fa34ea2011-04-15 13:00:20 -0700680 '--upload must be a gs:// URL.')
681
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700682 if options.private:
683 if options.sync_host:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700684 Usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700685 'together, we do not support private host prebuilts')
686
David James8ece7ee2011-06-29 16:02:30 -0700687 if not options.upload or not options.upload.startswith('gs://'):
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700688 Usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700689 '--upload must be a gs:// URL.')
690
691 if options.binhost_base_url != _BINHOST_BASE_URL:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700692 Usage(parser, 'Error: when using --private the --binhost-base-url '
David Jamese2488642011-11-14 16:15:20 -0800693 'is automatically derived.')
David James27fa7d12011-06-29 17:24:14 -0700694
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700695 return options, target
David Jamesc0f158a2011-02-22 16:07:29 -0800696
David Jamesc5cbd472012-06-19 16:25:45 -0700697def main(_argv):
David Jamesdb401072011-06-10 12:17:16 -0700698 # Set umask to a sane value so that files created as root are readable.
699 os.umask(022)
700
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700701 options, target = ParseOptions()
David Jamesc0f158a2011-02-22 16:07:29 -0800702
David James05bcb2b2011-02-09 09:25:47 -0800703 # Calculate a list of Packages index files to compare against. Whenever we
704 # upload a package, we check to make sure it's not already stored in one of
705 # the packages files we uploaded. This list of packages files might contain
706 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800707 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800708
David James8ece7ee2011-06-29 16:02:30 -0700709 if options.set_version:
710 version = options.set_version
711 else:
712 version = GetVersion()
David Jamesc0f158a2011-02-22 16:07:29 -0800713 if options.prepend_version:
714 version = '%s-%s' % (options.prepend_version, version)
715
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700716 acl = 'public-read'
717 binhost_base_url = options.binhost_base_url
718
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700719 if target and options.private:
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700720 binhost_base_url = options.upload
721 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700722 target)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700723 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
724
725 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David James615e5b52011-06-03 11:10:15 -0700726 pkg_indexes, options.build_path,
David James27fa7d12011-06-29 17:24:14 -0700727 options.packages, options.skip_upload,
David Jamese2488642011-11-14 16:15:20 -0800728 options.binhost_conf_dir, options.debug,
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700729 target, options.slave_targets)
David Jamesc0f158a2011-02-22 16:07:29 -0800730
David James8c846492011-01-25 17:07:29 -0800731 if options.sync_host:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700732 uploader.SyncHostPrebuilts(version, options.key, options.git_sync,
733 options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800734
Chris Sosa62c8ff52012-06-04 15:03:12 -0700735 if options.board or options.slave_targets:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700736 uploader.SyncBoardPrebuilts(version, options.key, options.git_sync,
737 options.sync_binhost_conf,
Zdenek Behan62a57792012-08-31 15:09:08 +0200738 options.upload_board_tarball,
739 options.prepackaged_tarball)