blob: 3050c4d629c94737c68a4dead9d6770fe3020c2a [file] [log] [blame]
David James8c846492011-01-25 17:07:29 -08001#!/usr/bin/python
Chris Sosac13bba52011-05-24 15:14:09 -07002# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
David James8c846492011-01-25 17:07:29 -08003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
David James8c846492011-01-25 17:07:29 -08006"""
7This script is used to upload host prebuilts as well as board BINHOSTS.
8
9If the URL starts with 'gs://', we upload using gsutil to Google Storage.
10Otherwise, rsync is used.
11
12After a build is successfully uploaded a file is updated with the proper
13BINHOST version as well as the target board. This file is defined in GIT_FILE
14
15
16To read more about prebuilts/binhost binary packages please refer to:
17http://sites/chromeos/for-team-members/engineering/releng/prebuilt-binaries-for-streamlining-the-build-process
18
19
20Example of uploading prebuilt amd64 host files to Google Storage:
21./prebuilt.py -p /b/cbuild/build -s -u gs://chromeos-prebuilt
22
23Example of uploading x86-dogfood binhosts to Google Storage:
24./prebuilt.py -b x86-dogfood -p /b/cbuild/build/ -u gs://chromeos-prebuilt -g
25
26Example of uploading prebuilt amd64 host files using rsync:
27./prebuilt.py -p /b/cbuild/build -s -u codf30.jail:/tmp
28"""
29
Chris Sosa1dc96132012-05-11 15:40:50 -070030import datetime
31import multiprocessing
32import optparse
33import os
34import sys
35import tempfile
36
37if __name__ == '__main__':
38 import constants
39 sys.path.insert(0, constants.SOURCE_ROOT)
40
41from chromite.lib import cros_build_lib
42from chromite.lib.binpkg import (GrabLocalPackageIndex, GrabRemotePackageIndex)
43
David James8c846492011-01-25 17:07:29 -080044_RETRIES = 3
45_GSUTIL_BIN = '/b/build/third_party/gsutil/gsutil'
46_HOST_PACKAGES_PATH = 'chroot/var/lib/portage/pkgs'
David James05bcb2b2011-02-09 09:25:47 -080047_CATEGORIES_PATH = 'chroot/etc/portage/categories'
David James615e5b52011-06-03 11:10:15 -070048_PYM_PATH = 'chroot/usr/lib/portage/pym'
David James4058b0d2011-12-08 21:24:50 -080049_HOST_ARCH = 'amd64'
David James8c846492011-01-25 17:07:29 -080050_BOARD_PATH = 'chroot/build/%(board)s'
David James4058b0d2011-12-08 21:24:50 -080051_REL_BOARD_PATH = 'board/%(target)s/%(version)s'
52_REL_HOST_PATH = 'host/%(host_arch)s/%(target)s/%(version)s'
David James8c846492011-01-25 17:07:29 -080053# Private overlays to look at for builds to filter
54# relative to build path
55_PRIVATE_OVERLAY_DIR = 'src/private-overlays'
Scott Zawalskiab1bed32011-03-16 15:24:24 -070056_GOOGLESTORAGE_ACL_FILE = 'googlestorage_acl.xml'
David Jamesce619292011-11-08 11:42:36 -080057_BINHOST_BASE_URL = 'https://commondatastorage.googleapis.com/chromeos-prebuilt'
David James8c846492011-01-25 17:07:29 -080058_PREBUILT_BASE_DIR = 'src/third_party/chromiumos-overlay/chromeos/config/'
59# Created in the event of new host targets becoming available
60_PREBUILT_MAKE_CONF = {'amd64': os.path.join(_PREBUILT_BASE_DIR,
61 'make.conf.amd64-host')}
62_BINHOST_CONF_DIR = 'src/third_party/chromiumos-overlay/chromeos/binhost'
63
64
David James8c846492011-01-25 17:07:29 -080065class UploadFailed(Exception):
66 """Raised when one of the files uploaded failed."""
67 pass
68
69class UnknownBoardFormat(Exception):
70 """Raised when a function finds an unknown board format."""
71 pass
72
David James8c846492011-01-25 17:07:29 -080073
David James4058b0d2011-12-08 21:24:50 -080074class BuildTarget(object):
75 """A board/variant/profile tuple."""
76
77 def __init__(self, board_variant, profile=None):
78 self.board_variant = board_variant
79 self.board, _, self.variant = board_variant.partition('_')
80 self.profile = profile
81
82 def __str__(self):
83 if self.profile:
84 return '%s_%s' % (self.board_variant, self.profile)
85 else:
86 return self.board_variant
87
88 def __eq__(self, other):
89 return str(other) == str(self)
90
91 def __hash__(self):
92 return hash(str(self))
93
94
David James8c846492011-01-25 17:07:29 -080095def UpdateLocalFile(filename, value, key='PORTAGE_BINHOST'):
96 """Update the key in file with the value passed.
97 File format:
98 key="value"
99 Note quotes are added automatically
100
101 Args:
102 filename: Name of file to modify.
103 value: Value to write with the key.
104 key: The variable key to update. (Default: PORTAGE_BINHOST)
105 """
106 if os.path.exists(filename):
107 file_fh = open(filename)
108 else:
109 file_fh = open(filename, 'w+')
110 file_lines = []
111 found = False
112 keyval_str = '%(key)s=%(value)s'
113 for line in file_fh:
114 # Strip newlines from end of line. We already add newlines below.
115 line = line.rstrip("\n")
116
117 if len(line.split('=')) != 2:
118 # Skip any line that doesn't fit key=val.
119 file_lines.append(line)
120 continue
121
122 file_var, file_val = line.split('=')
123 if file_var == key:
124 found = True
David James20b2b6f2011-11-18 15:11:58 -0800125 print 'Updating %s=%s to %s="%s"' % (file_var, file_val, key, value)
David James8c846492011-01-25 17:07:29 -0800126 value = '"%s"' % value
127 file_lines.append(keyval_str % {'key': key, 'value': value})
128 else:
129 file_lines.append(keyval_str % {'key': file_var, 'value': file_val})
130
131 if not found:
132 file_lines.append(keyval_str % {'key': key, 'value': value})
133
134 file_fh.close()
135 # write out new file
136 new_file_fh = open(filename, 'w')
137 new_file_fh.write('\n'.join(file_lines) + '\n')
138 new_file_fh.close()
139
140
David James27fa7d12011-06-29 17:24:14 -0700141def RevGitFile(filename, value, retries=5, key='PORTAGE_BINHOST', dryrun=False):
David James8c846492011-01-25 17:07:29 -0800142 """Update and push the git file.
143
144 Args:
145 filename: file to modify that is in a git repo already
146 value: string representing the version of the prebuilt that has been
147 uploaded.
148 retries: The number of times to retry before giving up, default: 5
149 key: The variable key to update in the git file.
150 (Default: PORTAGE_BINHOST)
151 """
152 prebuilt_branch = 'prebuilt_branch'
David James1b6e67a2011-05-19 21:32:38 -0700153 cwd = os.path.abspath(os.path.dirname(filename))
154 commit = cros_build_lib.RunCommand(['git', 'rev-parse', 'HEAD'], cwd=cwd,
Peter Mayofe0e6872011-04-20 00:52:08 -0400155 redirect_stdout=True).output.rstrip()
David James8c846492011-01-25 17:07:29 -0800156 description = 'Update %s="%s" in %s' % (key, value, filename)
157 print description
David James66009462012-03-25 10:08:38 -0700158
David James8c846492011-01-25 17:07:29 -0800159 try:
David James66009462012-03-25 10:08:38 -0700160 cros_build_lib.CreatePushBranch(prebuilt_branch, cwd)
David James8c846492011-01-25 17:07:29 -0800161 UpdateLocalFile(filename, value, key)
David James1b6e67a2011-05-19 21:32:38 -0700162 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
163 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James66009462012-03-25 10:08:38 -0700164 cros_build_lib.GitPushWithRetry(prebuilt_branch, cwd=cwd, dryrun=dryrun,
165 retries=retries)
David James8c846492011-01-25 17:07:29 -0800166 finally:
David James1b6e67a2011-05-19 21:32:38 -0700167 cros_build_lib.RunCommand(['git', 'checkout', commit], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800168
169
170def GetVersion():
171 """Get the version to put in LATEST and update the git version with."""
172 return datetime.datetime.now().strftime('%d.%m.%y.%H%M%S')
173
174
David James8c846492011-01-25 17:07:29 -0800175def _GsUpload(args):
176 """Upload to GS bucket.
177
178 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800179 args: a tuple of three arguments that contains local_file, remote_file, and
180 the acl used for uploading the file.
David James8c846492011-01-25 17:07:29 -0800181
182 Returns:
183 Return the arg tuple of two if the upload failed
184 """
David Jamesfd0b0852011-02-23 11:15:36 -0800185 (local_file, remote_file, acl) = args
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700186 CANNED_ACLS = ['public-read', 'private', 'bucket-owner-read',
187 'authenticated-read', 'bucket-owner-full-control',
188 'public-read-write']
189 acl_cmd = None
190 if acl in CANNED_ACLS:
Peter Mayo193f68f2011-04-19 19:08:21 -0400191 cmd = [_GSUTIL_BIN, 'cp', '-a', acl, local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700192 else:
193 # For private uploads we assume that the overlay board is set up properly
194 # and a googlestore_acl.xml is present, if not this script errors
Peter Mayo193f68f2011-04-19 19:08:21 -0400195 cmd = [_GSUTIL_BIN, 'cp', '-a', 'private', local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700196 if not os.path.exists(acl):
197 print >> sys.stderr, ('You are specifying either a file that does not '
198 'exist or an unknown canned acl: %s. Aborting '
199 'upload') % acl
200 # emulate the failing of an upload since we are not uploading the file
201 return (local_file, remote_file)
David James8c846492011-01-25 17:07:29 -0800202
Peter Mayo193f68f2011-04-19 19:08:21 -0400203 acl_cmd = [_GSUTIL_BIN, 'setacl', acl, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700204
Chris Sosa1dc96132012-05-11 15:40:50 -0700205 if not cros_build_lib.RunCommandWithRetries(
206 _RETRIES, cmd, print_cmd=False, error_code_ok=True).returncode == 0:
David James8c846492011-01-25 17:07:29 -0800207 return (local_file, remote_file)
208
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700209 if acl_cmd:
210 # Apply the passed in ACL xml file to the uploaded object.
Chris Sosa1dc96132012-05-11 15:40:50 -0700211 cros_build_lib.RunCommandWithRetries(_RETRIES, acl_cmd, print_cmd=False,
212 error_code_ok=True)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700213
214
David Jamesfd0b0852011-02-23 11:15:36 -0800215def RemoteUpload(acl, files, pool=10):
David James8c846492011-01-25 17:07:29 -0800216 """Upload to google storage.
217
218 Create a pool of process and call _GsUpload with the proper arguments.
219
220 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800221 acl: The canned acl used for uploading. acl can be one of: "public-read",
222 "public-read-write", "authenticated-read", "bucket-owner-read",
223 "bucket-owner-full-control", or "private".
David James8c846492011-01-25 17:07:29 -0800224 files: dictionary with keys to local files and values to remote path.
225 pool: integer of maximum proesses to have at the same time.
226
227 Returns:
228 Return a set of tuple arguments of the failed uploads
229 """
230 # TODO(scottz) port this to use _RunManyParallel when it is available in
231 # cros_build_lib
232 pool = multiprocessing.Pool(processes=pool)
233 workers = []
234 for local_file, remote_path in files.iteritems():
David Jamesfd0b0852011-02-23 11:15:36 -0800235 workers.append((local_file, remote_path, acl))
David James8c846492011-01-25 17:07:29 -0800236
237 result = pool.map_async(_GsUpload, workers, chunksize=1)
238 while True:
239 try:
Chris Sosa471532a2011-02-01 15:10:06 -0800240 return set(result.get(60 * 60))
David James8c846492011-01-25 17:07:29 -0800241 except multiprocessing.TimeoutError:
242 pass
243
244
245def GenerateUploadDict(base_local_path, base_remote_path, pkgs):
246 """Build a dictionary of local remote file key pairs to upload.
247
248 Args:
249 base_local_path: The base path to the files on the local hard drive.
250 remote_path: The base path to the remote paths.
251 pkgs: The packages to upload.
252
253 Returns:
254 Returns a dictionary of local_path/remote_path pairs
255 """
256 upload_files = {}
257 for pkg in pkgs:
258 suffix = pkg['CPV'] + '.tbz2'
259 local_path = os.path.join(base_local_path, suffix)
260 assert os.path.exists(local_path)
261 remote_path = '%s/%s' % (base_remote_path.rstrip('/'), suffix)
262 upload_files[local_path] = remote_path
263
264 return upload_files
265
266def GetBoardPathFromCrosOverlayList(build_path, target):
267 """Use the cros_overlay_list to determine the path to the board overlay
268 Args:
269 build_path: The path to the root of the build directory
270 target: The target that we are looking for, could consist of board and
271 board_variant, we handle that properly
272 Returns:
273 The last line from cros_overlay_list as a string
274 """
Chris Sosa471532a2011-02-01 15:10:06 -0800275 script_dir = os.path.join(build_path, 'src/platform/dev/host')
David James4058b0d2011-12-08 21:24:50 -0800276 cmd = ['./cros_overlay_list', '--board', target.board]
277 if target.variant:
278 cmd += ['--variant', target.variant]
David James8c846492011-01-25 17:07:29 -0800279
280 cmd_output = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
281 cwd=script_dir)
282 # We only care about the last entry
283 return cmd_output.output.splitlines().pop()
284
285
286def DeterminePrebuiltConfFile(build_path, target):
287 """Determine the prebuilt.conf file that needs to be updated for prebuilts.
288
289 Args:
290 build_path: The path to the root of the build directory
291 target: String representation of the board. This includes host and board
292 targets
293
294 Returns
295 A string path to a prebuilt.conf file to be updated.
296 """
David James4058b0d2011-12-08 21:24:50 -0800297 if _HOST_ARCH == target:
David James8c846492011-01-25 17:07:29 -0800298 # We are host.
299 # Without more examples of hosts this is a kludge for now.
300 # TODO(Scottz): as new host targets come online expand this to
301 # work more like boards.
Chris Sosa471532a2011-02-01 15:10:06 -0800302 make_path = _PREBUILT_MAKE_CONF[target]
David James8c846492011-01-25 17:07:29 -0800303 else:
304 # We are a board
305 board = GetBoardPathFromCrosOverlayList(build_path, target)
306 make_path = os.path.join(board, 'prebuilt.conf')
307
308 return make_path
309
310
311def UpdateBinhostConfFile(path, key, value):
312 """Update binhost config file file with key=value.
313
314 Args:
315 path: Filename to update.
316 key: Key to update.
317 value: New value for key.
318 """
319 cwd = os.path.dirname(os.path.abspath(path))
320 filename = os.path.basename(path)
321 if not os.path.isdir(cwd):
322 os.makedirs(cwd)
323 if not os.path.isfile(path):
324 config_file = file(path, 'w')
David James8c846492011-01-25 17:07:29 -0800325 config_file.close()
326 UpdateLocalFile(path, value, key)
Chris Sosac13bba52011-05-24 15:14:09 -0700327 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
David James20b2b6f2011-11-18 15:11:58 -0800328 description = 'Update %s="%s" in %s' % (key, value, filename)
Peter Mayo193f68f2011-04-19 19:08:21 -0400329 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800330
331
David Jamesce093af2011-02-23 15:21:58 -0800332def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800333 """Grab all of the packages files associated with a list of binhost_urls.
334
David James05bcb2b2011-02-09 09:25:47 -0800335 Args:
336 binhost_urls: The URLs for the directories containing the Packages files we
337 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800338
339 Returns:
340 A list of PackageIndex objects.
341 """
342 pkg_indexes = []
343 for url in binhost_urls:
344 pkg_index = GrabRemotePackageIndex(url)
345 if pkg_index:
346 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800347 return pkg_indexes
348
349
David James05bcb2b2011-02-09 09:25:47 -0800350
David Jamesc0f158a2011-02-22 16:07:29 -0800351class PrebuiltUploader(object):
352 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800353
David James615e5b52011-06-03 11:10:15 -0700354 def __init__(self, upload_location, acl, binhost_base_url,
David James32b0b2f2011-07-13 20:56:50 -0700355 pkg_indexes, build_path, packages, skip_upload,
David James4058b0d2011-12-08 21:24:50 -0800356 binhost_conf_dir, debug, target, slave_targets):
David Jamesc0f158a2011-02-22 16:07:29 -0800357 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800358
David Jamesc0f158a2011-02-22 16:07:29 -0800359 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800360
David Jamesc0f158a2011-02-22 16:07:29 -0800361 Args:
362 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800363 acl: The canned acl used for uploading to Google Storage. acl can be one
364 of: "public-read", "public-read-write", "authenticated-read",
365 "bucket-owner-read", "bucket-owner-full-control", or "private". If
366 we are not uploading to Google Storage, this parameter is unused.
367 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800368 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
369 uploading duplicate files, we just link to the old files.
David James615e5b52011-06-03 11:10:15 -0700370 build_path: The path to the directory containing the chroot.
371 packages: Packages to upload.
David James32b0b2f2011-07-13 20:56:50 -0700372 skip_upload: Don't actually upload the tarballs.
373 binhost_conf_dir: Directory where to store binhost.conf files.
374 debug: Don't push or upload prebuilts.
David James4058b0d2011-12-08 21:24:50 -0800375 target: BuildTarget managed by this builder.
376 slave_targets: List of BuildTargets managed by slave builders.
David Jamesc0f158a2011-02-22 16:07:29 -0800377 """
378 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800379 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800380 self._binhost_base_url = binhost_base_url
381 self._pkg_indexes = pkg_indexes
David James615e5b52011-06-03 11:10:15 -0700382 self._build_path = build_path
383 self._packages = set(packages)
David James8ece7ee2011-06-29 16:02:30 -0700384 self._skip_upload = skip_upload
David James32b0b2f2011-07-13 20:56:50 -0700385 self._binhost_conf_dir = binhost_conf_dir
David James27fa7d12011-06-29 17:24:14 -0700386 self._debug = debug
David James4058b0d2011-12-08 21:24:50 -0800387 self._target = target
388 self._slave_targets = slave_targets
David James615e5b52011-06-03 11:10:15 -0700389
390 def _ShouldFilterPackage(self, pkg):
391 if not self._packages:
392 return False
393 pym_path = os.path.abspath(os.path.join(self._build_path, _PYM_PATH))
David James710b7dc2012-02-07 16:49:59 -0800394 sys.path.insert(0, pym_path)
David James615e5b52011-06-03 11:10:15 -0700395 import portage.versions
396 cat, pkgname = portage.versions.catpkgsplit(pkg['CPV'])[0:2]
397 cp = '%s/%s' % (cat, pkgname)
398 return pkgname not in self._packages and cp not in self._packages
David James8c846492011-01-25 17:07:29 -0800399
David Jamesc0f158a2011-02-22 16:07:29 -0800400 def _UploadPrebuilt(self, package_path, url_suffix):
401 """Upload host or board prebuilt files to Google Storage space.
David James8c846492011-01-25 17:07:29 -0800402
David Jamesc0f158a2011-02-22 16:07:29 -0800403 Args:
404 package_path: The path to the packages dir.
David Jamesce093af2011-02-23 15:21:58 -0800405 url_suffix: The remote subdirectory where we should upload the packages.
David Jamesa3bba142011-05-26 21:24:20 -0700406
David Jamesc0f158a2011-02-22 16:07:29 -0800407 """
David James8c846492011-01-25 17:07:29 -0800408
David Jamesc0f158a2011-02-22 16:07:29 -0800409 # Process Packages file, removing duplicates and filtered packages.
410 pkg_index = GrabLocalPackageIndex(package_path)
411 pkg_index.SetUploadLocation(self._binhost_base_url, url_suffix)
David James615e5b52011-06-03 11:10:15 -0700412 pkg_index.RemoveFilteredPackages(self._ShouldFilterPackage)
David Jamesc0f158a2011-02-22 16:07:29 -0800413 uploads = pkg_index.ResolveDuplicateUploads(self._pkg_indexes)
David James05bcb2b2011-02-09 09:25:47 -0800414
David Jamesc0f158a2011-02-22 16:07:29 -0800415 # Write Packages file.
416 tmp_packages_file = pkg_index.WriteToNamedTemporaryFile()
David James05bcb2b2011-02-09 09:25:47 -0800417
David Jamesc0f158a2011-02-22 16:07:29 -0800418 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
419 if remote_location.startswith('gs://'):
420 # Build list of files to upload.
421 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
422 remote_file = '%s/Packages' % remote_location.rstrip('/')
423 upload_files[tmp_packages_file.name] = remote_file
David James05bcb2b2011-02-09 09:25:47 -0800424
David Jamesfd0b0852011-02-23 11:15:36 -0800425 failed_uploads = RemoteUpload(self._acl, upload_files)
David Jamesc0f158a2011-02-22 16:07:29 -0800426 if len(failed_uploads) > 1 or (None not in failed_uploads):
David Jamesf4db1122011-03-17 16:18:05 -0700427 error_msg = ['%s -> %s\n' % args for args in failed_uploads if args]
David Jamesc0f158a2011-02-22 16:07:29 -0800428 raise UploadFailed('Error uploading:\n%s' % error_msg)
429 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400430 pkgs = [p['CPV'] + '.tbz2' for p in uploads]
David Jamesc0f158a2011-02-22 16:07:29 -0800431 ssh_server, remote_path = remote_location.split(':', 1)
Peter Mayo193f68f2011-04-19 19:08:21 -0400432 remote_path = remote_path.rstrip('/')
433 pkg_index = tmp_packages_file.name
434 remote_location = remote_location.rstrip('/')
435 remote_packages = '%s/Packages' % remote_location
436 cmds = [['ssh', ssh_server, 'mkdir', '-p', remote_path],
437 ['rsync', '-av', '--chmod=a+r', pkg_index, remote_packages]]
David Jamesc0f158a2011-02-22 16:07:29 -0800438 if pkgs:
Peter Mayo193f68f2011-04-19 19:08:21 -0400439 cmds.append(['rsync', '-Rav'] + pkgs + [remote_location + '/'])
David Jamesc0f158a2011-02-22 16:07:29 -0800440 for cmd in cmds:
Chris Sosa1dc96132012-05-11 15:40:50 -0700441 try:
442 cros_build_lib.RunCommandWithRetries(_RETRIES, cmd, cwd=package_path)
443 except cros_build_lib.RunCommandError:
444 raise UploadFailed('Could not run %s' % cmd)
David James8c846492011-01-25 17:07:29 -0800445
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200446 def _UploadBoardTarball(self, board_path, url_suffix, version):
David James8fa34ea2011-04-15 13:00:20 -0700447 """Upload a tarball of the board at the specified path to Google Storage.
448
449 Args:
450 board_path: The path to the board dir.
451 url_suffix: The remote subdirectory where we should upload the packages.
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200452 version: The version of the board.
David James8fa34ea2011-04-15 13:00:20 -0700453 """
454 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
455 assert remote_location.startswith('gs://')
456 cwd, boardname = os.path.split(board_path.rstrip(os.path.sep))
457 tmpdir = tempfile.mkdtemp()
458 try:
459 tarfile = os.path.join(tmpdir, '%s.tbz2' % boardname)
Brian Harringd223a242012-02-03 20:12:10 -0800460 cmd = ['tar', '-I', 'pbzip2', '-cf', tarfile]
David James8fa34ea2011-04-15 13:00:20 -0700461 excluded_paths = ('usr/lib/debug', 'usr/local/autotest', 'packages',
462 'tmp')
463 for path in excluded_paths:
Zdenek Behane3ed3462011-06-16 00:36:08 +0200464 cmd.append('--exclude=%s/*' % path)
465 cmd.append('.')
Brian Harringd223a242012-02-03 20:12:10 -0800466 cros_build_lib.SudoRunCommand(cmd, cwd=os.path.join(cwd, boardname))
David James8fa34ea2011-04-15 13:00:20 -0700467 remote_tarfile = '%s/%s.tbz2' % (remote_location.rstrip('/'), boardname)
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200468 # FIXME(zbehan): Temporary hack to upload amd64-host chroots to a
469 # different gs bucket. The right way is to do the upload in a separate
470 # pass of this script.
471 if boardname == 'amd64-host':
Zdenek Behanaf3c9002011-06-24 10:07:40 +0200472 # FIXME(zbehan): Why does version contain the prefix "chroot-"?
473 remote_tarfile = \
474 'gs://chromiumos-sdk/cros-sdk-%s.tbz2' % version.strip('chroot-')
David James8fa34ea2011-04-15 13:00:20 -0700475 if _GsUpload((tarfile, remote_tarfile, self._acl)):
476 sys.exit(1)
477 finally:
Brian Harringd223a242012-02-03 20:12:10 -0800478 cros_build_lib.SudoRunCommand(['rm', '-rf', tmpdir], cwd=cwd)
David James8fa34ea2011-04-15 13:00:20 -0700479
Chris Sosa1dc96132012-05-11 15:40:50 -0700480 def _GetTargets(self):
481 """Retuns the list of targets to use."""
482 targets = self._slave_targets[:]
483 if self._target:
484 targets.append(self._target)
485
486 return targets
487
488 def SyncHostPrebuilts(self, version, key, git_sync, sync_binhost_conf):
David Jamesc0f158a2011-02-22 16:07:29 -0800489 """Synchronize host prebuilt files.
David James05bcb2b2011-02-09 09:25:47 -0800490
David Jamesc0f158a2011-02-22 16:07:29 -0800491 This function will sync both the standard host packages, plus the host
492 packages associated with all targets that have been "setup" with the
493 current host's chroot. For instance, if this host has been used to build
494 x86-generic, it will sync the host packages associated with
495 'i686-pc-linux-gnu'. If this host has also been used to build arm-generic,
496 it will also sync the host packages associated with
497 'armv7a-cros-linux-gnueabi'.
David James05bcb2b2011-02-09 09:25:47 -0800498
David Jamesc0f158a2011-02-22 16:07:29 -0800499 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800500 version: A unique string, intended to be included in the upload path,
501 which identifies the version number of the uploaded prebuilts.
502 key: The variable key to update in the git file.
503 git_sync: If set, update make.conf of target to reference the latest
504 prebuilt packages generated here.
505 sync_binhost_conf: If set, update binhost config file in
506 chromiumos-overlay for the host.
507 """
David Jamese2488642011-11-14 16:15:20 -0800508 # Slave boards are listed before the master board so that the master board
509 # takes priority (i.e. x86-generic preflight host prebuilts takes priority
510 # over preflight host prebuilts from other builders.)
511 binhost_urls = []
Chris Sosa1dc96132012-05-11 15:40:50 -0700512 for target in self._GetTargets():
David James4058b0d2011-12-08 21:24:50 -0800513 url_suffix = _REL_HOST_PATH % {'version': version,
514 'host_arch': _HOST_ARCH,
515 'target': target}
David Jamese2488642011-11-14 16:15:20 -0800516 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James05bcb2b2011-02-09 09:25:47 -0800517
David James4058b0d2011-12-08 21:24:50 -0800518 if self._target == target and not self._skip_upload and not self._debug:
David Jamese2488642011-11-14 16:15:20 -0800519 # Upload prebuilts.
520 package_path = os.path.join(self._build_path, _HOST_PACKAGES_PATH)
521 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8ece7ee2011-06-29 16:02:30 -0700522
David Jamese2488642011-11-14 16:15:20 -0800523 # Record URL where prebuilts were uploaded.
524 binhost_urls.append('%s/%s/' % (self._binhost_base_url.rstrip('/'),
525 packages_url_suffix.rstrip('/')))
526
David James20b2b6f2011-11-18 15:11:58 -0800527 binhost = ' '.join(binhost_urls)
David James8ece7ee2011-06-29 16:02:30 -0700528 if git_sync:
529 git_file = os.path.join(self._build_path,
David James4058b0d2011-12-08 21:24:50 -0800530 _PREBUILT_MAKE_CONF[_HOST_ARCH])
David Jamese2488642011-11-14 16:15:20 -0800531 RevGitFile(git_file, binhost, key=key, dryrun=self._debug)
David James8ece7ee2011-06-29 16:02:30 -0700532 if sync_binhost_conf:
David James32b0b2f2011-07-13 20:56:50 -0700533 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James4058b0d2011-12-08 21:24:50 -0800534 'host', '%s-%s.conf' % (_HOST_ARCH, key))
David Jamese2488642011-11-14 16:15:20 -0800535 UpdateBinhostConfFile(binhost_conf, key, binhost)
David Jamesc0f158a2011-02-22 16:07:29 -0800536
Chris Sosa1dc96132012-05-11 15:40:50 -0700537 def SyncBoardPrebuilts(self, version, key, git_sync, sync_binhost_conf,
538 upload_board_tarball):
David Jamesc0f158a2011-02-22 16:07:29 -0800539 """Synchronize board prebuilt files.
540
541 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800542 version: A unique string, intended to be included in the upload path,
543 which identifies the version number of the uploaded prebuilts.
544 key: The variable key to update in the git file.
545 git_sync: If set, update make.conf of target to reference the latest
546 prebuilt packages generated here.
547 sync_binhost_conf: If set, update binhost config file in
548 chromiumos-overlay for the current board.
David James8fa34ea2011-04-15 13:00:20 -0700549 upload_board_tarball: Include a tarball of the board in our upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800550 """
Chris Sosa1dc96132012-05-11 15:40:50 -0700551 for target in self._GetTargets():
David Jamese2488642011-11-14 16:15:20 -0800552 board_path = os.path.join(self._build_path,
David James4058b0d2011-12-08 21:24:50 -0800553 _BOARD_PATH % {'board': target.board_variant})
David Jamese2488642011-11-14 16:15:20 -0800554 package_path = os.path.join(board_path, 'packages')
David James4058b0d2011-12-08 21:24:50 -0800555 url_suffix = _REL_BOARD_PATH % {'target': target, 'version': version}
David Jamese2488642011-11-14 16:15:20 -0800556 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James8fa34ea2011-04-15 13:00:20 -0700557
David James4058b0d2011-12-08 21:24:50 -0800558 if self._target == target and not self._skip_upload and not self._debug:
David Jamese2488642011-11-14 16:15:20 -0800559 # Upload board tarballs in the background.
560 if upload_board_tarball:
561 tar_process = multiprocessing.Process(target=self._UploadBoardTarball,
562 args=(board_path, url_suffix,
563 version))
564 tar_process.start()
David James8fa34ea2011-04-15 13:00:20 -0700565
David Jamese2488642011-11-14 16:15:20 -0800566 # Upload prebuilts.
567 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8fa34ea2011-04-15 13:00:20 -0700568
David Jamese2488642011-11-14 16:15:20 -0800569 # Make sure we finished uploading the board tarballs.
570 if upload_board_tarball:
571 tar_process.join()
572 assert tar_process.exitcode == 0
573 # TODO(zbehan): This should be done cleaner.
David James4058b0d2011-12-08 21:24:50 -0800574 if target.board == 'amd64-host':
David Jamese2488642011-11-14 16:15:20 -0800575 sdk_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James8ece7ee2011-06-29 16:02:30 -0700576 'host/sdk_version.conf')
David Jamese2488642011-11-14 16:15:20 -0800577 RevGitFile(sdk_conf, version.strip('chroot-'),
578 key='SDK_LATEST_VERSION', dryrun=self._debug)
David Jamesc0f158a2011-02-22 16:07:29 -0800579
David Jamese2488642011-11-14 16:15:20 -0800580 # Record URL where prebuilts were uploaded.
581 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
582 packages_url_suffix.rstrip('/'))
583
584 if git_sync:
David James4058b0d2011-12-08 21:24:50 -0800585 git_file = DeterminePrebuiltConfFile(self._build_path, target)
David Jamese2488642011-11-14 16:15:20 -0800586 RevGitFile(git_file, url_value, key=key, dryrun=self._debug)
587 if sync_binhost_conf:
588 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James4058b0d2011-12-08 21:24:50 -0800589 'target', '%s-%s.conf' % (target, key))
David Jamese2488642011-11-14 16:15:20 -0800590 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800591
592
Chris Sosa1dc96132012-05-11 15:40:50 -0700593def Usage(parser, msg):
David James8c846492011-01-25 17:07:29 -0800594 """Display usage message and parser help then exit with 1."""
595 print >> sys.stderr, msg
596 parser.print_help()
597 sys.exit(1)
598
David James4058b0d2011-12-08 21:24:50 -0800599
Chris Sosa1dc96132012-05-11 15:40:50 -0700600def _AddSlaveBoard(_option, _opt_str, value, parser):
601 """Callback that adds a slave board to the list of slave targets."""
David James4058b0d2011-12-08 21:24:50 -0800602 parser.values.slave_targets.append(BuildTarget(value))
603
604
Chris Sosa1dc96132012-05-11 15:40:50 -0700605def _AddSlaveProfile(_option, _opt_str, value, parser):
606 """Callback that adds a slave profile to the list of slave targets."""
David James4058b0d2011-12-08 21:24:50 -0800607 if not parser.values.slave_targets:
Chris Sosa1dc96132012-05-11 15:40:50 -0700608 Usage(parser, 'Must specify --slave-board before --slave-profile')
David James4058b0d2011-12-08 21:24:50 -0800609 if parser.values.slave_targets[-1].profile is not None:
Chris Sosa1dc96132012-05-11 15:40:50 -0700610 Usage(parser, 'Cannot specify --slave-profile twice for same board')
David James4058b0d2011-12-08 21:24:50 -0800611 parser.values.slave_targets[-1].profile = value
612
613
David Jamesc0f158a2011-02-22 16:07:29 -0800614def ParseOptions():
Chris Sosa1dc96132012-05-11 15:40:50 -0700615 """Returns options given by the user and the target specified.
616
617 Returns a tuple containing a parsed options object and BuildTarget.
618 target instance is None if no board is specified.
619 """
David James8c846492011-01-25 17:07:29 -0800620 parser = optparse.OptionParser()
621 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
622 default=_BINHOST_BASE_URL,
623 help='Base URL to use for binhost in make.conf updates')
624 parser.add_option('', '--previous-binhost-url', action='append',
625 default=[], dest='previous_binhost_url',
626 help='Previous binhost URL')
627 parser.add_option('-b', '--board', dest='board', default=None,
628 help='Board type that was built on this machine')
David James4058b0d2011-12-08 21:24:50 -0800629 parser.add_option('', '--profile', dest='profile', default=None,
630 help='Profile that was built on this machine')
631 parser.add_option('', '--slave-board', default=[], action='callback',
632 dest='slave_targets', type='string',
Chris Sosa1dc96132012-05-11 15:40:50 -0700633 callback=_AddSlaveBoard,
David James4058b0d2011-12-08 21:24:50 -0800634 help='Board type that was built on a slave machine. To '
635 'add a profile to this board, use --slave-profile.')
636 parser.add_option('', '--slave-profile', action='callback', type='string',
Chris Sosa1dc96132012-05-11 15:40:50 -0700637 callback=_AddSlaveProfile,
David James4058b0d2011-12-08 21:24:50 -0800638 help='Board profile that was built on a slave machine. '
639 'Applies to previous slave board.')
David James8c846492011-01-25 17:07:29 -0800640 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800641 help='Path to the directory containing the chroot')
David James615e5b52011-06-03 11:10:15 -0700642 parser.add_option('', '--packages', action='append',
643 default=[], dest='packages',
644 help='Only include the specified packages. '
645 '(Default is to include all packages.)')
David James8c846492011-01-25 17:07:29 -0800646 parser.add_option('-s', '--sync-host', dest='sync_host',
647 default=False, action='store_true',
648 help='Sync host prebuilts')
649 parser.add_option('-g', '--git-sync', dest='git_sync',
650 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800651 help='Enable git version sync (This commits to a repo.) '
652 'This is used by full builders to commit directly '
653 'to board overlays.')
David James8c846492011-01-25 17:07:29 -0800654 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')
David James8ece7ee2011-06-29 16:02:30 -0700666 parser.add_option('', '--set-version', dest='set_version',
667 default=None,
668 help='Specify the version string')
David James8c846492011-01-25 17:07:29 -0800669 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
670 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800671 help='Update binhost.conf in chromiumos-overlay or '
672 'chromeos-overlay. Commit the changes, but don\'t '
673 'push them. This is used for preflight binhosts.')
David James32b0b2f2011-07-13 20:56:50 -0700674 parser.add_option('', '--binhost-conf-dir', dest='binhost_conf_dir',
675 default=_BINHOST_CONF_DIR,
676 help='Directory to commit binhost config with '
677 '--sync-binhost-conf.')
David Jamesfd0b0852011-02-23 11:15:36 -0800678 parser.add_option('-P', '--private', dest='private', action='store_true',
679 default=False, help='Mark gs:// uploads as private.')
David James8ece7ee2011-06-29 16:02:30 -0700680 parser.add_option('', '--skip-upload', dest='skip_upload',
681 action='store_true', default=False,
682 help='Skip upload step.')
David James8fa34ea2011-04-15 13:00:20 -0700683 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
684 action='store_true', default=False,
685 help='Upload board tarball to Google Storage.')
David James27fa7d12011-06-29 17:24:14 -0700686 parser.add_option('', '--debug', dest='debug',
687 action='store_true', default=False,
688 help='Don\'t push or upload prebuilts.')
David James8c846492011-01-25 17:07:29 -0800689
690 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800691 if not options.build_path:
Chris Sosa1dc96132012-05-11 15:40:50 -0700692 Usage(parser, 'Error: you need provide a chroot path')
David James8ece7ee2011-06-29 16:02:30 -0700693 if not options.upload and not options.skip_upload:
Chris Sosa1dc96132012-05-11 15:40:50 -0700694 Usage(parser, 'Error: you need to provide an upload location using -u')
David James8ece7ee2011-06-29 16:02:30 -0700695 if not options.set_version and options.skip_upload:
Chris Sosa1dc96132012-05-11 15:40:50 -0700696 Usage(parser, 'Error: If you are using --skip-upload, you must specify a '
David James8ece7ee2011-06-29 16:02:30 -0700697 'version number using --set-version.')
David James9417f272011-05-26 13:24:47 -0700698 if args:
Chris Sosa1dc96132012-05-11 15:40:50 -0700699 Usage(parser, 'Error: invalid arguments passed to prebuilt.py: %r' % args)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700700
Chris Sosa1dc96132012-05-11 15:40:50 -0700701 target = None
702 if options.board:
703 target = BuildTarget(options.board, options.profile)
704
705 if target in options.slave_targets:
706 Usage(parser, 'Error: --board/--profile must not also be a slave target.')
David Jamese2488642011-11-14 16:15:20 -0800707
David James4058b0d2011-12-08 21:24:50 -0800708 if len(set(options.slave_targets)) != len(options.slave_targets):
Chris Sosa1dc96132012-05-11 15:40:50 -0700709 Usage(parser, 'Error: --slave-boards must not have duplicates.')
David Jamese2488642011-11-14 16:15:20 -0800710
David James4058b0d2011-12-08 21:24:50 -0800711 if options.slave_targets and options.git_sync:
Chris Sosa1dc96132012-05-11 15:40:50 -0700712 Usage(parser, 'Error: --slave-boards is not compatible with --git-sync')
David Jamese2488642011-11-14 16:15:20 -0800713
David James8ece7ee2011-06-29 16:02:30 -0700714 if (options.upload_board_tarball and options.skip_upload and
715 options.board == 'amd64-host'):
Chris Sosa1dc96132012-05-11 15:40:50 -0700716 Usage(parser, 'Error: --skip-upload is not compatible with '
David James8ece7ee2011-06-29 16:02:30 -0700717 '--upload-board-tarball and --board=amd64-host')
David James8fa34ea2011-04-15 13:00:20 -0700718
David James8ece7ee2011-06-29 16:02:30 -0700719 if (options.upload_board_tarball and not options.skip_upload and
720 not options.upload.startswith('gs://')):
Chris Sosa1dc96132012-05-11 15:40:50 -0700721 Usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
David James8fa34ea2011-04-15 13:00:20 -0700722 '--upload must be a gs:// URL.')
723
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700724 if options.private:
725 if options.sync_host:
Chris Sosa1dc96132012-05-11 15:40:50 -0700726 Usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700727 'together, we do not support private host prebuilts')
728
David James8ece7ee2011-06-29 16:02:30 -0700729 if not options.upload or not options.upload.startswith('gs://'):
Chris Sosa1dc96132012-05-11 15:40:50 -0700730 Usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700731 '--upload must be a gs:// URL.')
732
733 if options.binhost_base_url != _BINHOST_BASE_URL:
Chris Sosa1dc96132012-05-11 15:40:50 -0700734 Usage(parser, 'Error: when using --private the --binhost-base-url '
David Jamese2488642011-11-14 16:15:20 -0800735 'is automatically derived.')
David James27fa7d12011-06-29 17:24:14 -0700736
Chris Sosa1dc96132012-05-11 15:40:50 -0700737 return options, target
David Jamesc0f158a2011-02-22 16:07:29 -0800738
739def main():
David Jamesdb401072011-06-10 12:17:16 -0700740 # Set umask to a sane value so that files created as root are readable.
741 os.umask(022)
742
Chris Sosa1dc96132012-05-11 15:40:50 -0700743 options, target = ParseOptions()
David Jamesc0f158a2011-02-22 16:07:29 -0800744
David James05bcb2b2011-02-09 09:25:47 -0800745 # Calculate a list of Packages index files to compare against. Whenever we
746 # upload a package, we check to make sure it's not already stored in one of
747 # the packages files we uploaded. This list of packages files might contain
748 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800749 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800750
David James8ece7ee2011-06-29 16:02:30 -0700751 if options.set_version:
752 version = options.set_version
753 else:
754 version = GetVersion()
David Jamesc0f158a2011-02-22 16:07:29 -0800755 if options.prepend_version:
756 version = '%s-%s' % (options.prepend_version, version)
757
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700758 acl = 'public-read'
759 binhost_base_url = options.binhost_base_url
760
Chris Sosa1dc96132012-05-11 15:40:50 -0700761 if target and options.private:
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700762 binhost_base_url = options.upload
763 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
Chris Sosa1dc96132012-05-11 15:40:50 -0700764 target)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700765 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
766
767 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David James615e5b52011-06-03 11:10:15 -0700768 pkg_indexes, options.build_path,
David James27fa7d12011-06-29 17:24:14 -0700769 options.packages, options.skip_upload,
David Jamese2488642011-11-14 16:15:20 -0800770 options.binhost_conf_dir, options.debug,
Chris Sosa1dc96132012-05-11 15:40:50 -0700771 target, options.slave_targets)
David Jamesc0f158a2011-02-22 16:07:29 -0800772
David James8c846492011-01-25 17:07:29 -0800773 if options.sync_host:
Chris Sosa1dc96132012-05-11 15:40:50 -0700774 uploader.SyncHostPrebuilts(version, options.key, options.git_sync,
775 options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800776
777 if options.board:
Chris Sosa1dc96132012-05-11 15:40:50 -0700778 assert target, 'Board specified but no target generated.'
779 uploader.SyncBoardPrebuilts(version, options.key, options.git_sync,
780 options.sync_binhost_conf,
781 options.upload_board_tarball)
David James8c846492011-01-25 17:07:29 -0800782
783if __name__ == '__main__':
784 main()