blob: b60c2a49651be06e2066060474fbf0f08c46dcb7 [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
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
8If the URL starts with 'gs://', we upload using gsutil to Google Storage.
9Otherwise, rsync is used.
10
11After a build is successfully uploaded a file is updated with the proper
12BINHOST version as well as the target board. This file is defined in GIT_FILE
13
14
15To read more about prebuilts/binhost binary packages please refer to:
16http://sites/chromeos/for-team-members/engineering/releng/prebuilt-binaries-for-streamlining-the-build-process
17
18
19Example of uploading prebuilt amd64 host files to Google Storage:
20./prebuilt.py -p /b/cbuild/build -s -u gs://chromeos-prebuilt
21
22Example of uploading x86-dogfood binhosts to Google Storage:
23./prebuilt.py -b x86-dogfood -p /b/cbuild/build/ -u gs://chromeos-prebuilt -g
24
25Example of uploading prebuilt amd64 host files using rsync:
26./prebuilt.py -p /b/cbuild/build -s -u codf30.jail:/tmp
27"""
28
Chris Sosa1dc96132012-05-11 15:40:50 -070029import datetime
30import multiprocessing
31import optparse
32import os
33import sys
34import tempfile
35
36if __name__ == '__main__':
37 import constants
38 sys.path.insert(0, constants.SOURCE_ROOT)
39
40from chromite.lib import cros_build_lib
Brian Harringaf019fb2012-05-10 15:06:13 -070041from chromite.lib import osutils
Chris Sosa1dc96132012-05-11 15:40:50 -070042from 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
Brian Harringaf019fb2012-05-10 15:06:13 -0700136 osutils.WriteFile(filename, '\n'.join(file_lines) + '\n')
David James8c846492011-01-25 17:07:29 -0800137
138
David James27fa7d12011-06-29 17:24:14 -0700139def RevGitFile(filename, value, retries=5, key='PORTAGE_BINHOST', dryrun=False):
David James8c846492011-01-25 17:07:29 -0800140 """Update and push the git file.
141
142 Args:
143 filename: file to modify that is in a git repo already
144 value: string representing the version of the prebuilt that has been
145 uploaded.
146 retries: The number of times to retry before giving up, default: 5
147 key: The variable key to update in the git file.
148 (Default: PORTAGE_BINHOST)
149 """
150 prebuilt_branch = 'prebuilt_branch'
David James1b6e67a2011-05-19 21:32:38 -0700151 cwd = os.path.abspath(os.path.dirname(filename))
152 commit = cros_build_lib.RunCommand(['git', 'rev-parse', 'HEAD'], cwd=cwd,
Peter Mayofe0e6872011-04-20 00:52:08 -0400153 redirect_stdout=True).output.rstrip()
David James8c846492011-01-25 17:07:29 -0800154 description = 'Update %s="%s" in %s' % (key, value, filename)
155 print description
David James66009462012-03-25 10:08:38 -0700156
David James8c846492011-01-25 17:07:29 -0800157 try:
David James66009462012-03-25 10:08:38 -0700158 cros_build_lib.CreatePushBranch(prebuilt_branch, cwd)
David James8c846492011-01-25 17:07:29 -0800159 UpdateLocalFile(filename, value, key)
David James1b6e67a2011-05-19 21:32:38 -0700160 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
161 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James66009462012-03-25 10:08:38 -0700162 cros_build_lib.GitPushWithRetry(prebuilt_branch, cwd=cwd, dryrun=dryrun,
163 retries=retries)
David James8c846492011-01-25 17:07:29 -0800164 finally:
David James1b6e67a2011-05-19 21:32:38 -0700165 cros_build_lib.RunCommand(['git', 'checkout', commit], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800166
167
168def GetVersion():
169 """Get the version to put in LATEST and update the git version with."""
170 return datetime.datetime.now().strftime('%d.%m.%y.%H%M%S')
171
172
David James8c846492011-01-25 17:07:29 -0800173def _GsUpload(args):
174 """Upload to GS bucket.
175
176 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800177 args: a tuple of three arguments that contains local_file, remote_file, and
178 the acl used for uploading the file.
David James8c846492011-01-25 17:07:29 -0800179
180 Returns:
181 Return the arg tuple of two if the upload failed
182 """
David Jamesfd0b0852011-02-23 11:15:36 -0800183 (local_file, remote_file, acl) = args
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700184 CANNED_ACLS = ['public-read', 'private', 'bucket-owner-read',
185 'authenticated-read', 'bucket-owner-full-control',
186 'public-read-write']
187 acl_cmd = None
188 if acl in CANNED_ACLS:
Peter Mayo193f68f2011-04-19 19:08:21 -0400189 cmd = [_GSUTIL_BIN, 'cp', '-a', acl, local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700190 else:
191 # For private uploads we assume that the overlay board is set up properly
192 # and a googlestore_acl.xml is present, if not this script errors
Peter Mayo193f68f2011-04-19 19:08:21 -0400193 cmd = [_GSUTIL_BIN, 'cp', '-a', 'private', local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700194 if not os.path.exists(acl):
195 print >> sys.stderr, ('You are specifying either a file that does not '
196 'exist or an unknown canned acl: %s. Aborting '
197 'upload') % acl
198 # emulate the failing of an upload since we are not uploading the file
199 return (local_file, remote_file)
David James8c846492011-01-25 17:07:29 -0800200
Peter Mayo193f68f2011-04-19 19:08:21 -0400201 acl_cmd = [_GSUTIL_BIN, 'setacl', acl, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700202
Chris Sosa1dc96132012-05-11 15:40:50 -0700203 if not cros_build_lib.RunCommandWithRetries(
204 _RETRIES, cmd, print_cmd=False, error_code_ok=True).returncode == 0:
David James8c846492011-01-25 17:07:29 -0800205 return (local_file, remote_file)
206
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700207 if acl_cmd:
208 # Apply the passed in ACL xml file to the uploaded object.
Chris Sosa1dc96132012-05-11 15:40:50 -0700209 cros_build_lib.RunCommandWithRetries(_RETRIES, acl_cmd, print_cmd=False,
210 error_code_ok=True)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700211
212
David Jamesfd0b0852011-02-23 11:15:36 -0800213def RemoteUpload(acl, files, pool=10):
David James8c846492011-01-25 17:07:29 -0800214 """Upload to google storage.
215
216 Create a pool of process and call _GsUpload with the proper arguments.
217
218 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800219 acl: The canned acl used for uploading. acl can be one of: "public-read",
220 "public-read-write", "authenticated-read", "bucket-owner-read",
221 "bucket-owner-full-control", or "private".
David James8c846492011-01-25 17:07:29 -0800222 files: dictionary with keys to local files and values to remote path.
223 pool: integer of maximum proesses to have at the same time.
224
225 Returns:
226 Return a set of tuple arguments of the failed uploads
227 """
228 # TODO(scottz) port this to use _RunManyParallel when it is available in
229 # cros_build_lib
230 pool = multiprocessing.Pool(processes=pool)
231 workers = []
232 for local_file, remote_path in files.iteritems():
David Jamesfd0b0852011-02-23 11:15:36 -0800233 workers.append((local_file, remote_path, acl))
David James8c846492011-01-25 17:07:29 -0800234
235 result = pool.map_async(_GsUpload, workers, chunksize=1)
236 while True:
237 try:
Chris Sosa471532a2011-02-01 15:10:06 -0800238 return set(result.get(60 * 60))
David James8c846492011-01-25 17:07:29 -0800239 except multiprocessing.TimeoutError:
240 pass
241
242
243def GenerateUploadDict(base_local_path, base_remote_path, pkgs):
244 """Build a dictionary of local remote file key pairs to upload.
245
246 Args:
247 base_local_path: The base path to the files on the local hard drive.
248 remote_path: The base path to the remote paths.
249 pkgs: The packages to upload.
250
251 Returns:
252 Returns a dictionary of local_path/remote_path pairs
253 """
254 upload_files = {}
255 for pkg in pkgs:
256 suffix = pkg['CPV'] + '.tbz2'
257 local_path = os.path.join(base_local_path, suffix)
258 assert os.path.exists(local_path)
259 remote_path = '%s/%s' % (base_remote_path.rstrip('/'), suffix)
260 upload_files[local_path] = remote_path
261
262 return upload_files
263
264def GetBoardPathFromCrosOverlayList(build_path, target):
265 """Use the cros_overlay_list to determine the path to the board overlay
266 Args:
267 build_path: The path to the root of the build directory
268 target: The target that we are looking for, could consist of board and
269 board_variant, we handle that properly
270 Returns:
271 The last line from cros_overlay_list as a string
272 """
Chris Sosa471532a2011-02-01 15:10:06 -0800273 script_dir = os.path.join(build_path, 'src/platform/dev/host')
David James4058b0d2011-12-08 21:24:50 -0800274 cmd = ['./cros_overlay_list', '--board', target.board]
275 if target.variant:
276 cmd += ['--variant', target.variant]
David James8c846492011-01-25 17:07:29 -0800277
278 cmd_output = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
279 cwd=script_dir)
280 # We only care about the last entry
281 return cmd_output.output.splitlines().pop()
282
283
284def DeterminePrebuiltConfFile(build_path, target):
285 """Determine the prebuilt.conf file that needs to be updated for prebuilts.
286
287 Args:
288 build_path: The path to the root of the build directory
289 target: String representation of the board. This includes host and board
290 targets
291
292 Returns
293 A string path to a prebuilt.conf file to be updated.
294 """
David James4058b0d2011-12-08 21:24:50 -0800295 if _HOST_ARCH == target:
David James8c846492011-01-25 17:07:29 -0800296 # We are host.
297 # Without more examples of hosts this is a kludge for now.
298 # TODO(Scottz): as new host targets come online expand this to
299 # work more like boards.
Chris Sosa471532a2011-02-01 15:10:06 -0800300 make_path = _PREBUILT_MAKE_CONF[target]
David James8c846492011-01-25 17:07:29 -0800301 else:
302 # We are a board
303 board = GetBoardPathFromCrosOverlayList(build_path, target)
304 make_path = os.path.join(board, 'prebuilt.conf')
305
306 return make_path
307
308
309def UpdateBinhostConfFile(path, key, value):
310 """Update binhost config file file with key=value.
311
312 Args:
313 path: Filename to update.
314 key: Key to update.
315 value: New value for key.
316 """
317 cwd = os.path.dirname(os.path.abspath(path))
318 filename = os.path.basename(path)
Brian Harringaf019fb2012-05-10 15:06:13 -0700319 osutils.SafeMakedirs(cwd)
320 osutils.WriteFile(path, '')
David James8c846492011-01-25 17:07:29 -0800321 UpdateLocalFile(path, value, key)
Chris Sosac13bba52011-05-24 15:14:09 -0700322 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
David James20b2b6f2011-11-18 15:11:58 -0800323 description = 'Update %s="%s" in %s' % (key, value, filename)
Peter Mayo193f68f2011-04-19 19:08:21 -0400324 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800325
326
David Jamesce093af2011-02-23 15:21:58 -0800327def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800328 """Grab all of the packages files associated with a list of binhost_urls.
329
David James05bcb2b2011-02-09 09:25:47 -0800330 Args:
331 binhost_urls: The URLs for the directories containing the Packages files we
332 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800333
334 Returns:
335 A list of PackageIndex objects.
336 """
337 pkg_indexes = []
338 for url in binhost_urls:
339 pkg_index = GrabRemotePackageIndex(url)
340 if pkg_index:
341 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800342 return pkg_indexes
343
344
David James05bcb2b2011-02-09 09:25:47 -0800345
David Jamesc0f158a2011-02-22 16:07:29 -0800346class PrebuiltUploader(object):
347 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800348
David James615e5b52011-06-03 11:10:15 -0700349 def __init__(self, upload_location, acl, binhost_base_url,
David James32b0b2f2011-07-13 20:56:50 -0700350 pkg_indexes, build_path, packages, skip_upload,
David James4058b0d2011-12-08 21:24:50 -0800351 binhost_conf_dir, debug, target, slave_targets):
David Jamesc0f158a2011-02-22 16:07:29 -0800352 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800353
David Jamesc0f158a2011-02-22 16:07:29 -0800354 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800355
David Jamesc0f158a2011-02-22 16:07:29 -0800356 Args:
357 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800358 acl: The canned acl used for uploading to Google Storage. acl can be one
359 of: "public-read", "public-read-write", "authenticated-read",
360 "bucket-owner-read", "bucket-owner-full-control", or "private". If
361 we are not uploading to Google Storage, this parameter is unused.
362 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800363 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
364 uploading duplicate files, we just link to the old files.
David James615e5b52011-06-03 11:10:15 -0700365 build_path: The path to the directory containing the chroot.
366 packages: Packages to upload.
David James32b0b2f2011-07-13 20:56:50 -0700367 skip_upload: Don't actually upload the tarballs.
368 binhost_conf_dir: Directory where to store binhost.conf files.
369 debug: Don't push or upload prebuilts.
David James4058b0d2011-12-08 21:24:50 -0800370 target: BuildTarget managed by this builder.
371 slave_targets: List of BuildTargets managed by slave builders.
David Jamesc0f158a2011-02-22 16:07:29 -0800372 """
373 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800374 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800375 self._binhost_base_url = binhost_base_url
376 self._pkg_indexes = pkg_indexes
David James615e5b52011-06-03 11:10:15 -0700377 self._build_path = build_path
378 self._packages = set(packages)
David James8ece7ee2011-06-29 16:02:30 -0700379 self._skip_upload = skip_upload
David James32b0b2f2011-07-13 20:56:50 -0700380 self._binhost_conf_dir = binhost_conf_dir
David James27fa7d12011-06-29 17:24:14 -0700381 self._debug = debug
David James4058b0d2011-12-08 21:24:50 -0800382 self._target = target
383 self._slave_targets = slave_targets
David James615e5b52011-06-03 11:10:15 -0700384
385 def _ShouldFilterPackage(self, pkg):
386 if not self._packages:
387 return False
388 pym_path = os.path.abspath(os.path.join(self._build_path, _PYM_PATH))
David James710b7dc2012-02-07 16:49:59 -0800389 sys.path.insert(0, pym_path)
David James615e5b52011-06-03 11:10:15 -0700390 import portage.versions
391 cat, pkgname = portage.versions.catpkgsplit(pkg['CPV'])[0:2]
392 cp = '%s/%s' % (cat, pkgname)
393 return pkgname not in self._packages and cp not in self._packages
David James8c846492011-01-25 17:07:29 -0800394
David Jamesc0f158a2011-02-22 16:07:29 -0800395 def _UploadPrebuilt(self, package_path, url_suffix):
396 """Upload host or board prebuilt files to Google Storage space.
David James8c846492011-01-25 17:07:29 -0800397
David Jamesc0f158a2011-02-22 16:07:29 -0800398 Args:
399 package_path: The path to the packages dir.
David Jamesce093af2011-02-23 15:21:58 -0800400 url_suffix: The remote subdirectory where we should upload the packages.
David Jamesa3bba142011-05-26 21:24:20 -0700401
David Jamesc0f158a2011-02-22 16:07:29 -0800402 """
David James8c846492011-01-25 17:07:29 -0800403
David Jamesc0f158a2011-02-22 16:07:29 -0800404 # Process Packages file, removing duplicates and filtered packages.
405 pkg_index = GrabLocalPackageIndex(package_path)
406 pkg_index.SetUploadLocation(self._binhost_base_url, url_suffix)
David James615e5b52011-06-03 11:10:15 -0700407 pkg_index.RemoveFilteredPackages(self._ShouldFilterPackage)
David Jamesc0f158a2011-02-22 16:07:29 -0800408 uploads = pkg_index.ResolveDuplicateUploads(self._pkg_indexes)
David James05bcb2b2011-02-09 09:25:47 -0800409
David Jamesc0f158a2011-02-22 16:07:29 -0800410 # Write Packages file.
411 tmp_packages_file = pkg_index.WriteToNamedTemporaryFile()
David James05bcb2b2011-02-09 09:25:47 -0800412
David Jamesc0f158a2011-02-22 16:07:29 -0800413 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
414 if remote_location.startswith('gs://'):
415 # Build list of files to upload.
416 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
417 remote_file = '%s/Packages' % remote_location.rstrip('/')
418 upload_files[tmp_packages_file.name] = remote_file
David James05bcb2b2011-02-09 09:25:47 -0800419
David Jamesfd0b0852011-02-23 11:15:36 -0800420 failed_uploads = RemoteUpload(self._acl, upload_files)
David Jamesc0f158a2011-02-22 16:07:29 -0800421 if len(failed_uploads) > 1 or (None not in failed_uploads):
David Jamesf4db1122011-03-17 16:18:05 -0700422 error_msg = ['%s -> %s\n' % args for args in failed_uploads if args]
David Jamesc0f158a2011-02-22 16:07:29 -0800423 raise UploadFailed('Error uploading:\n%s' % error_msg)
424 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400425 pkgs = [p['CPV'] + '.tbz2' for p in uploads]
David Jamesc0f158a2011-02-22 16:07:29 -0800426 ssh_server, remote_path = remote_location.split(':', 1)
Peter Mayo193f68f2011-04-19 19:08:21 -0400427 remote_path = remote_path.rstrip('/')
428 pkg_index = tmp_packages_file.name
429 remote_location = remote_location.rstrip('/')
430 remote_packages = '%s/Packages' % remote_location
431 cmds = [['ssh', ssh_server, 'mkdir', '-p', remote_path],
432 ['rsync', '-av', '--chmod=a+r', pkg_index, remote_packages]]
David Jamesc0f158a2011-02-22 16:07:29 -0800433 if pkgs:
Peter Mayo193f68f2011-04-19 19:08:21 -0400434 cmds.append(['rsync', '-Rav'] + pkgs + [remote_location + '/'])
David Jamesc0f158a2011-02-22 16:07:29 -0800435 for cmd in cmds:
Chris Sosa1dc96132012-05-11 15:40:50 -0700436 try:
437 cros_build_lib.RunCommandWithRetries(_RETRIES, cmd, cwd=package_path)
438 except cros_build_lib.RunCommandError:
439 raise UploadFailed('Could not run %s' % cmd)
David James8c846492011-01-25 17:07:29 -0800440
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200441 def _UploadBoardTarball(self, board_path, url_suffix, version):
David James8fa34ea2011-04-15 13:00:20 -0700442 """Upload a tarball of the board at the specified path to Google Storage.
443
444 Args:
445 board_path: The path to the board dir.
446 url_suffix: The remote subdirectory where we should upload the packages.
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200447 version: The version of the board.
David James8fa34ea2011-04-15 13:00:20 -0700448 """
449 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
450 assert remote_location.startswith('gs://')
451 cwd, boardname = os.path.split(board_path.rstrip(os.path.sep))
452 tmpdir = tempfile.mkdtemp()
453 try:
454 tarfile = os.path.join(tmpdir, '%s.tbz2' % boardname)
Brian Harringd223a242012-02-03 20:12:10 -0800455 cmd = ['tar', '-I', 'pbzip2', '-cf', tarfile]
David James8fa34ea2011-04-15 13:00:20 -0700456 excluded_paths = ('usr/lib/debug', 'usr/local/autotest', 'packages',
457 'tmp')
458 for path in excluded_paths:
Zdenek Behane3ed3462011-06-16 00:36:08 +0200459 cmd.append('--exclude=%s/*' % path)
460 cmd.append('.')
Brian Harringd223a242012-02-03 20:12:10 -0800461 cros_build_lib.SudoRunCommand(cmd, cwd=os.path.join(cwd, boardname))
David James8fa34ea2011-04-15 13:00:20 -0700462 remote_tarfile = '%s/%s.tbz2' % (remote_location.rstrip('/'), boardname)
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200463 # FIXME(zbehan): Temporary hack to upload amd64-host chroots to a
464 # different gs bucket. The right way is to do the upload in a separate
465 # pass of this script.
466 if boardname == 'amd64-host':
Zdenek Behanaf3c9002011-06-24 10:07:40 +0200467 # FIXME(zbehan): Why does version contain the prefix "chroot-"?
468 remote_tarfile = \
469 'gs://chromiumos-sdk/cros-sdk-%s.tbz2' % version.strip('chroot-')
David James8fa34ea2011-04-15 13:00:20 -0700470 if _GsUpload((tarfile, remote_tarfile, self._acl)):
471 sys.exit(1)
472 finally:
Brian Harringd223a242012-02-03 20:12:10 -0800473 cros_build_lib.SudoRunCommand(['rm', '-rf', tmpdir], cwd=cwd)
David James8fa34ea2011-04-15 13:00:20 -0700474
Chris Sosa1dc96132012-05-11 15:40:50 -0700475 def _GetTargets(self):
476 """Retuns the list of targets to use."""
477 targets = self._slave_targets[:]
478 if self._target:
479 targets.append(self._target)
480
481 return targets
482
483 def SyncHostPrebuilts(self, version, key, git_sync, sync_binhost_conf):
David Jamesc0f158a2011-02-22 16:07:29 -0800484 """Synchronize host prebuilt files.
David James05bcb2b2011-02-09 09:25:47 -0800485
David Jamesc0f158a2011-02-22 16:07:29 -0800486 This function will sync both the standard host packages, plus the host
487 packages associated with all targets that have been "setup" with the
488 current host's chroot. For instance, if this host has been used to build
489 x86-generic, it will sync the host packages associated with
490 'i686-pc-linux-gnu'. If this host has also been used to build arm-generic,
491 it will also sync the host packages associated with
492 'armv7a-cros-linux-gnueabi'.
David James05bcb2b2011-02-09 09:25:47 -0800493
David Jamesc0f158a2011-02-22 16:07:29 -0800494 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800495 version: A unique string, intended to be included in the upload path,
496 which identifies the version number of the uploaded prebuilts.
497 key: The variable key to update in the git file.
498 git_sync: If set, update make.conf of target to reference the latest
499 prebuilt packages generated here.
500 sync_binhost_conf: If set, update binhost config file in
501 chromiumos-overlay for the host.
502 """
David Jamese2488642011-11-14 16:15:20 -0800503 # Slave boards are listed before the master board so that the master board
504 # takes priority (i.e. x86-generic preflight host prebuilts takes priority
505 # over preflight host prebuilts from other builders.)
506 binhost_urls = []
Chris Sosa1dc96132012-05-11 15:40:50 -0700507 for target in self._GetTargets():
David James4058b0d2011-12-08 21:24:50 -0800508 url_suffix = _REL_HOST_PATH % {'version': version,
509 'host_arch': _HOST_ARCH,
510 'target': target}
David Jamese2488642011-11-14 16:15:20 -0800511 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James05bcb2b2011-02-09 09:25:47 -0800512
David James4058b0d2011-12-08 21:24:50 -0800513 if self._target == target and not self._skip_upload and not self._debug:
David Jamese2488642011-11-14 16:15:20 -0800514 # Upload prebuilts.
515 package_path = os.path.join(self._build_path, _HOST_PACKAGES_PATH)
516 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8ece7ee2011-06-29 16:02:30 -0700517
David Jamese2488642011-11-14 16:15:20 -0800518 # Record URL where prebuilts were uploaded.
519 binhost_urls.append('%s/%s/' % (self._binhost_base_url.rstrip('/'),
520 packages_url_suffix.rstrip('/')))
521
David James20b2b6f2011-11-18 15:11:58 -0800522 binhost = ' '.join(binhost_urls)
David James8ece7ee2011-06-29 16:02:30 -0700523 if git_sync:
524 git_file = os.path.join(self._build_path,
David James4058b0d2011-12-08 21:24:50 -0800525 _PREBUILT_MAKE_CONF[_HOST_ARCH])
David Jamese2488642011-11-14 16:15:20 -0800526 RevGitFile(git_file, binhost, key=key, dryrun=self._debug)
David James8ece7ee2011-06-29 16:02:30 -0700527 if sync_binhost_conf:
David James32b0b2f2011-07-13 20:56:50 -0700528 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James4058b0d2011-12-08 21:24:50 -0800529 'host', '%s-%s.conf' % (_HOST_ARCH, key))
David Jamese2488642011-11-14 16:15:20 -0800530 UpdateBinhostConfFile(binhost_conf, key, binhost)
David Jamesc0f158a2011-02-22 16:07:29 -0800531
Chris Sosa1dc96132012-05-11 15:40:50 -0700532 def SyncBoardPrebuilts(self, version, key, git_sync, sync_binhost_conf,
533 upload_board_tarball):
David Jamesc0f158a2011-02-22 16:07:29 -0800534 """Synchronize board prebuilt files.
535
536 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800537 version: A unique string, intended to be included in the upload path,
538 which identifies the version number of the uploaded prebuilts.
539 key: The variable key to update in the git file.
540 git_sync: If set, update make.conf of target to reference the latest
541 prebuilt packages generated here.
542 sync_binhost_conf: If set, update binhost config file in
543 chromiumos-overlay for the current board.
David James8fa34ea2011-04-15 13:00:20 -0700544 upload_board_tarball: Include a tarball of the board in our upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800545 """
Chris Sosa1dc96132012-05-11 15:40:50 -0700546 for target in self._GetTargets():
David Jamese2488642011-11-14 16:15:20 -0800547 board_path = os.path.join(self._build_path,
David James4058b0d2011-12-08 21:24:50 -0800548 _BOARD_PATH % {'board': target.board_variant})
David Jamese2488642011-11-14 16:15:20 -0800549 package_path = os.path.join(board_path, 'packages')
David James4058b0d2011-12-08 21:24:50 -0800550 url_suffix = _REL_BOARD_PATH % {'target': target, 'version': version}
David Jamese2488642011-11-14 16:15:20 -0800551 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James8fa34ea2011-04-15 13:00:20 -0700552
David James4058b0d2011-12-08 21:24:50 -0800553 if self._target == target and not self._skip_upload and not self._debug:
David Jamese2488642011-11-14 16:15:20 -0800554 # Upload board tarballs in the background.
555 if upload_board_tarball:
556 tar_process = multiprocessing.Process(target=self._UploadBoardTarball,
557 args=(board_path, url_suffix,
558 version))
559 tar_process.start()
David James8fa34ea2011-04-15 13:00:20 -0700560
David Jamese2488642011-11-14 16:15:20 -0800561 # Upload prebuilts.
562 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8fa34ea2011-04-15 13:00:20 -0700563
David Jamese2488642011-11-14 16:15:20 -0800564 # Make sure we finished uploading the board tarballs.
565 if upload_board_tarball:
566 tar_process.join()
567 assert tar_process.exitcode == 0
568 # TODO(zbehan): This should be done cleaner.
David James4058b0d2011-12-08 21:24:50 -0800569 if target.board == 'amd64-host':
David Jamese2488642011-11-14 16:15:20 -0800570 sdk_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James8ece7ee2011-06-29 16:02:30 -0700571 'host/sdk_version.conf')
David Jamese2488642011-11-14 16:15:20 -0800572 RevGitFile(sdk_conf, version.strip('chroot-'),
573 key='SDK_LATEST_VERSION', dryrun=self._debug)
David Jamesc0f158a2011-02-22 16:07:29 -0800574
David Jamese2488642011-11-14 16:15:20 -0800575 # Record URL where prebuilts were uploaded.
576 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
577 packages_url_suffix.rstrip('/'))
578
579 if git_sync:
David James4058b0d2011-12-08 21:24:50 -0800580 git_file = DeterminePrebuiltConfFile(self._build_path, target)
David Jamese2488642011-11-14 16:15:20 -0800581 RevGitFile(git_file, url_value, key=key, dryrun=self._debug)
582 if sync_binhost_conf:
583 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James4058b0d2011-12-08 21:24:50 -0800584 'target', '%s-%s.conf' % (target, key))
David Jamese2488642011-11-14 16:15:20 -0800585 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800586
587
Chris Sosa1dc96132012-05-11 15:40:50 -0700588def Usage(parser, msg):
David James8c846492011-01-25 17:07:29 -0800589 """Display usage message and parser help then exit with 1."""
590 print >> sys.stderr, msg
591 parser.print_help()
592 sys.exit(1)
593
David James4058b0d2011-12-08 21:24:50 -0800594
Chris Sosa1dc96132012-05-11 15:40:50 -0700595def _AddSlaveBoard(_option, _opt_str, value, parser):
596 """Callback that adds a slave board to the list of slave targets."""
David James4058b0d2011-12-08 21:24:50 -0800597 parser.values.slave_targets.append(BuildTarget(value))
598
599
Chris Sosa1dc96132012-05-11 15:40:50 -0700600def _AddSlaveProfile(_option, _opt_str, value, parser):
601 """Callback that adds a slave profile to the list of slave targets."""
David James4058b0d2011-12-08 21:24:50 -0800602 if not parser.values.slave_targets:
Chris Sosa1dc96132012-05-11 15:40:50 -0700603 Usage(parser, 'Must specify --slave-board before --slave-profile')
David James4058b0d2011-12-08 21:24:50 -0800604 if parser.values.slave_targets[-1].profile is not None:
Chris Sosa1dc96132012-05-11 15:40:50 -0700605 Usage(parser, 'Cannot specify --slave-profile twice for same board')
David James4058b0d2011-12-08 21:24:50 -0800606 parser.values.slave_targets[-1].profile = value
607
608
David Jamesc0f158a2011-02-22 16:07:29 -0800609def ParseOptions():
Chris Sosa1dc96132012-05-11 15:40:50 -0700610 """Returns options given by the user and the target specified.
611
612 Returns a tuple containing a parsed options object and BuildTarget.
613 target instance is None if no board is specified.
614 """
David James8c846492011-01-25 17:07:29 -0800615 parser = optparse.OptionParser()
616 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
617 default=_BINHOST_BASE_URL,
618 help='Base URL to use for binhost in make.conf updates')
619 parser.add_option('', '--previous-binhost-url', action='append',
620 default=[], dest='previous_binhost_url',
621 help='Previous binhost URL')
622 parser.add_option('-b', '--board', dest='board', default=None,
623 help='Board type that was built on this machine')
David James4058b0d2011-12-08 21:24:50 -0800624 parser.add_option('', '--profile', dest='profile', default=None,
625 help='Profile that was built on this machine')
626 parser.add_option('', '--slave-board', default=[], action='callback',
627 dest='slave_targets', type='string',
Chris Sosa1dc96132012-05-11 15:40:50 -0700628 callback=_AddSlaveBoard,
David James4058b0d2011-12-08 21:24:50 -0800629 help='Board type that was built on a slave machine. To '
630 'add a profile to this board, use --slave-profile.')
631 parser.add_option('', '--slave-profile', action='callback', type='string',
Chris Sosa1dc96132012-05-11 15:40:50 -0700632 callback=_AddSlaveProfile,
David James4058b0d2011-12-08 21:24:50 -0800633 help='Board profile that was built on a slave machine. '
634 'Applies to previous slave board.')
David James8c846492011-01-25 17:07:29 -0800635 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800636 help='Path to the directory containing the chroot')
David James615e5b52011-06-03 11:10:15 -0700637 parser.add_option('', '--packages', action='append',
638 default=[], dest='packages',
639 help='Only include the specified packages. '
640 '(Default is to include all packages.)')
David James8c846492011-01-25 17:07:29 -0800641 parser.add_option('-s', '--sync-host', dest='sync_host',
642 default=False, action='store_true',
643 help='Sync host prebuilts')
644 parser.add_option('-g', '--git-sync', dest='git_sync',
645 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800646 help='Enable git version sync (This commits to a repo.) '
647 'This is used by full builders to commit directly '
648 'to board overlays.')
David James8c846492011-01-25 17:07:29 -0800649 parser.add_option('-u', '--upload', dest='upload',
650 default=None,
651 help='Upload location')
652 parser.add_option('-V', '--prepend-version', dest='prepend_version',
653 default=None,
654 help='Add an identifier to the front of the version')
655 parser.add_option('-f', '--filters', dest='filters', action='store_true',
656 default=False,
657 help='Turn on filtering of private ebuild packages')
658 parser.add_option('-k', '--key', dest='key',
659 default='PORTAGE_BINHOST',
660 help='Key to update in make.conf / binhost.conf')
David James8ece7ee2011-06-29 16:02:30 -0700661 parser.add_option('', '--set-version', dest='set_version',
662 default=None,
663 help='Specify the version string')
David James8c846492011-01-25 17:07:29 -0800664 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
665 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800666 help='Update binhost.conf in chromiumos-overlay or '
667 'chromeos-overlay. Commit the changes, but don\'t '
668 'push them. This is used for preflight binhosts.')
David James32b0b2f2011-07-13 20:56:50 -0700669 parser.add_option('', '--binhost-conf-dir', dest='binhost_conf_dir',
670 default=_BINHOST_CONF_DIR,
671 help='Directory to commit binhost config with '
672 '--sync-binhost-conf.')
David Jamesfd0b0852011-02-23 11:15:36 -0800673 parser.add_option('-P', '--private', dest='private', action='store_true',
674 default=False, help='Mark gs:// uploads as private.')
David James8ece7ee2011-06-29 16:02:30 -0700675 parser.add_option('', '--skip-upload', dest='skip_upload',
676 action='store_true', default=False,
677 help='Skip upload step.')
David James8fa34ea2011-04-15 13:00:20 -0700678 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
679 action='store_true', default=False,
680 help='Upload board tarball to Google Storage.')
David James27fa7d12011-06-29 17:24:14 -0700681 parser.add_option('', '--debug', dest='debug',
682 action='store_true', default=False,
683 help='Don\'t push or upload prebuilts.')
David James8c846492011-01-25 17:07:29 -0800684
685 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800686 if not options.build_path:
Chris Sosa1dc96132012-05-11 15:40:50 -0700687 Usage(parser, 'Error: you need provide a chroot path')
David James8ece7ee2011-06-29 16:02:30 -0700688 if not options.upload and not options.skip_upload:
Chris Sosa1dc96132012-05-11 15:40:50 -0700689 Usage(parser, 'Error: you need to provide an upload location using -u')
David James8ece7ee2011-06-29 16:02:30 -0700690 if not options.set_version and options.skip_upload:
Chris Sosa1dc96132012-05-11 15:40:50 -0700691 Usage(parser, 'Error: If you are using --skip-upload, you must specify a '
David James8ece7ee2011-06-29 16:02:30 -0700692 'version number using --set-version.')
David James9417f272011-05-26 13:24:47 -0700693 if args:
Chris Sosa1dc96132012-05-11 15:40:50 -0700694 Usage(parser, 'Error: invalid arguments passed to prebuilt.py: %r' % args)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700695
Chris Sosa1dc96132012-05-11 15:40:50 -0700696 target = None
697 if options.board:
698 target = BuildTarget(options.board, options.profile)
699
700 if target in options.slave_targets:
701 Usage(parser, 'Error: --board/--profile must not also be a slave target.')
David Jamese2488642011-11-14 16:15:20 -0800702
David James4058b0d2011-12-08 21:24:50 -0800703 if len(set(options.slave_targets)) != len(options.slave_targets):
Chris Sosa1dc96132012-05-11 15:40:50 -0700704 Usage(parser, 'Error: --slave-boards must not have duplicates.')
David Jamese2488642011-11-14 16:15:20 -0800705
David James4058b0d2011-12-08 21:24:50 -0800706 if options.slave_targets and options.git_sync:
Chris Sosa1dc96132012-05-11 15:40:50 -0700707 Usage(parser, 'Error: --slave-boards is not compatible with --git-sync')
David Jamese2488642011-11-14 16:15:20 -0800708
David James8ece7ee2011-06-29 16:02:30 -0700709 if (options.upload_board_tarball and options.skip_upload and
710 options.board == 'amd64-host'):
Chris Sosa1dc96132012-05-11 15:40:50 -0700711 Usage(parser, 'Error: --skip-upload is not compatible with '
David James8ece7ee2011-06-29 16:02:30 -0700712 '--upload-board-tarball and --board=amd64-host')
David James8fa34ea2011-04-15 13:00:20 -0700713
David James8ece7ee2011-06-29 16:02:30 -0700714 if (options.upload_board_tarball and not options.skip_upload and
715 not options.upload.startswith('gs://')):
Chris Sosa1dc96132012-05-11 15:40:50 -0700716 Usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
David James8fa34ea2011-04-15 13:00:20 -0700717 '--upload must be a gs:// URL.')
718
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700719 if options.private:
720 if options.sync_host:
Chris Sosa1dc96132012-05-11 15:40:50 -0700721 Usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700722 'together, we do not support private host prebuilts')
723
David James8ece7ee2011-06-29 16:02:30 -0700724 if not options.upload or not options.upload.startswith('gs://'):
Chris Sosa1dc96132012-05-11 15:40:50 -0700725 Usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700726 '--upload must be a gs:// URL.')
727
728 if options.binhost_base_url != _BINHOST_BASE_URL:
Chris Sosa1dc96132012-05-11 15:40:50 -0700729 Usage(parser, 'Error: when using --private the --binhost-base-url '
David Jamese2488642011-11-14 16:15:20 -0800730 'is automatically derived.')
David James27fa7d12011-06-29 17:24:14 -0700731
Chris Sosa1dc96132012-05-11 15:40:50 -0700732 return options, target
David Jamesc0f158a2011-02-22 16:07:29 -0800733
734def main():
David Jamesdb401072011-06-10 12:17:16 -0700735 # Set umask to a sane value so that files created as root are readable.
736 os.umask(022)
737
Chris Sosa1dc96132012-05-11 15:40:50 -0700738 options, target = ParseOptions()
David Jamesc0f158a2011-02-22 16:07:29 -0800739
David James05bcb2b2011-02-09 09:25:47 -0800740 # Calculate a list of Packages index files to compare against. Whenever we
741 # upload a package, we check to make sure it's not already stored in one of
742 # the packages files we uploaded. This list of packages files might contain
743 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800744 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800745
David James8ece7ee2011-06-29 16:02:30 -0700746 if options.set_version:
747 version = options.set_version
748 else:
749 version = GetVersion()
David Jamesc0f158a2011-02-22 16:07:29 -0800750 if options.prepend_version:
751 version = '%s-%s' % (options.prepend_version, version)
752
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700753 acl = 'public-read'
754 binhost_base_url = options.binhost_base_url
755
Chris Sosa1dc96132012-05-11 15:40:50 -0700756 if target and options.private:
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700757 binhost_base_url = options.upload
758 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
Chris Sosa1dc96132012-05-11 15:40:50 -0700759 target)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700760 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
761
762 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David James615e5b52011-06-03 11:10:15 -0700763 pkg_indexes, options.build_path,
David James27fa7d12011-06-29 17:24:14 -0700764 options.packages, options.skip_upload,
David Jamese2488642011-11-14 16:15:20 -0800765 options.binhost_conf_dir, options.debug,
Chris Sosa1dc96132012-05-11 15:40:50 -0700766 target, options.slave_targets)
David Jamesc0f158a2011-02-22 16:07:29 -0800767
David James8c846492011-01-25 17:07:29 -0800768 if options.sync_host:
Chris Sosa1dc96132012-05-11 15:40:50 -0700769 uploader.SyncHostPrebuilts(version, options.key, options.git_sync,
770 options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800771
772 if options.board:
Chris Sosa1dc96132012-05-11 15:40:50 -0700773 assert target, 'Board specified but no target generated.'
774 uploader.SyncBoardPrebuilts(version, options.key, options.git_sync,
775 options.sync_binhost_conf,
776 options.upload_board_tarball)
David James8c846492011-01-25 17:07:29 -0800777
778if __name__ == '__main__':
779 main()