blob: 0e451974795c2e351634a72a2bbe479fee9de259 [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
6import datetime
7import multiprocessing
8import optparse
9import os
David James8c846492011-01-25 17:07:29 -080010import sys
11import tempfile
David James8c846492011-01-25 17:07:29 -080012
Chris Sosa471532a2011-02-01 15:10:06 -080013if __name__ == '__main__':
14 import constants
David James710b7dc2012-02-07 16:49:59 -080015 sys.path.insert(0, constants.SOURCE_ROOT)
Chris Sosa471532a2011-02-01 15:10:06 -080016
David James8c846492011-01-25 17:07:29 -080017from chromite.lib import cros_build_lib
Chris Sosac13bba52011-05-24 15:14:09 -070018from chromite.lib.binpkg import (GrabLocalPackageIndex, GrabRemotePackageIndex)
David James8c846492011-01-25 17:07:29 -080019"""
20This script is used to upload host prebuilts as well as board BINHOSTS.
21
22If the URL starts with 'gs://', we upload using gsutil to Google Storage.
23Otherwise, rsync is used.
24
25After a build is successfully uploaded a file is updated with the proper
26BINHOST version as well as the target board. This file is defined in GIT_FILE
27
28
29To read more about prebuilts/binhost binary packages please refer to:
30http://sites/chromeos/for-team-members/engineering/releng/prebuilt-binaries-for-streamlining-the-build-process
31
32
33Example of uploading prebuilt amd64 host files to Google Storage:
34./prebuilt.py -p /b/cbuild/build -s -u gs://chromeos-prebuilt
35
36Example of uploading x86-dogfood binhosts to Google Storage:
37./prebuilt.py -b x86-dogfood -p /b/cbuild/build/ -u gs://chromeos-prebuilt -g
38
39Example of uploading prebuilt amd64 host files using rsync:
40./prebuilt.py -p /b/cbuild/build -s -u codf30.jail:/tmp
41"""
42
David James8c846492011-01-25 17:07:29 -080043_RETRIES = 3
44_GSUTIL_BIN = '/b/build/third_party/gsutil/gsutil'
45_HOST_PACKAGES_PATH = 'chroot/var/lib/portage/pkgs'
David James05bcb2b2011-02-09 09:25:47 -080046_CATEGORIES_PATH = 'chroot/etc/portage/categories'
David James615e5b52011-06-03 11:10:15 -070047_PYM_PATH = 'chroot/usr/lib/portage/pym'
David James4058b0d2011-12-08 21:24:50 -080048_HOST_ARCH = 'amd64'
David James8c846492011-01-25 17:07:29 -080049_BOARD_PATH = 'chroot/build/%(board)s'
David James4058b0d2011-12-08 21:24:50 -080050_REL_BOARD_PATH = 'board/%(target)s/%(version)s'
51_REL_HOST_PATH = 'host/%(host_arch)s/%(target)s/%(version)s'
David James8c846492011-01-25 17:07:29 -080052# Private overlays to look at for builds to filter
53# relative to build path
54_PRIVATE_OVERLAY_DIR = 'src/private-overlays'
Scott Zawalskiab1bed32011-03-16 15:24:24 -070055_GOOGLESTORAGE_ACL_FILE = 'googlestorage_acl.xml'
David Jamesce619292011-11-08 11:42:36 -080056_BINHOST_BASE_URL = 'https://commondatastorage.googleapis.com/chromeos-prebuilt'
David James8c846492011-01-25 17:07:29 -080057_PREBUILT_BASE_DIR = 'src/third_party/chromiumos-overlay/chromeos/config/'
58# Created in the event of new host targets becoming available
59_PREBUILT_MAKE_CONF = {'amd64': os.path.join(_PREBUILT_BASE_DIR,
60 'make.conf.amd64-host')}
61_BINHOST_CONF_DIR = 'src/third_party/chromiumos-overlay/chromeos/binhost'
62
63
David James8c846492011-01-25 17:07:29 -080064class UploadFailed(Exception):
65 """Raised when one of the files uploaded failed."""
66 pass
67
68class UnknownBoardFormat(Exception):
69 """Raised when a function finds an unknown board format."""
70 pass
71
David James8c846492011-01-25 17:07:29 -080072
David James4058b0d2011-12-08 21:24:50 -080073class BuildTarget(object):
74 """A board/variant/profile tuple."""
75
76 def __init__(self, board_variant, profile=None):
77 self.board_variant = board_variant
78 self.board, _, self.variant = board_variant.partition('_')
79 self.profile = profile
80
81 def __str__(self):
82 if self.profile:
83 return '%s_%s' % (self.board_variant, self.profile)
84 else:
85 return self.board_variant
86
87 def __eq__(self, other):
88 return str(other) == str(self)
89
90 def __hash__(self):
91 return hash(str(self))
92
93
David James8c846492011-01-25 17:07:29 -080094def UpdateLocalFile(filename, value, key='PORTAGE_BINHOST'):
95 """Update the key in file with the value passed.
96 File format:
97 key="value"
98 Note quotes are added automatically
99
100 Args:
101 filename: Name of file to modify.
102 value: Value to write with the key.
103 key: The variable key to update. (Default: PORTAGE_BINHOST)
104 """
105 if os.path.exists(filename):
106 file_fh = open(filename)
107 else:
108 file_fh = open(filename, 'w+')
109 file_lines = []
110 found = False
111 keyval_str = '%(key)s=%(value)s'
112 for line in file_fh:
113 # Strip newlines from end of line. We already add newlines below.
114 line = line.rstrip("\n")
115
116 if len(line.split('=')) != 2:
117 # Skip any line that doesn't fit key=val.
118 file_lines.append(line)
119 continue
120
121 file_var, file_val = line.split('=')
122 if file_var == key:
123 found = True
David James20b2b6f2011-11-18 15:11:58 -0800124 print 'Updating %s=%s to %s="%s"' % (file_var, file_val, key, value)
David James8c846492011-01-25 17:07:29 -0800125 value = '"%s"' % value
126 file_lines.append(keyval_str % {'key': key, 'value': value})
127 else:
128 file_lines.append(keyval_str % {'key': file_var, 'value': file_val})
129
130 if not found:
131 file_lines.append(keyval_str % {'key': key, 'value': value})
132
133 file_fh.close()
134 # write out new file
135 new_file_fh = open(filename, 'w')
136 new_file_fh.write('\n'.join(file_lines) + '\n')
137 new_file_fh.close()
138
139
David James27fa7d12011-06-29 17:24:14 -0700140def RevGitFile(filename, value, retries=5, key='PORTAGE_BINHOST', dryrun=False):
David James8c846492011-01-25 17:07:29 -0800141 """Update and push the git file.
142
143 Args:
144 filename: file to modify that is in a git repo already
145 value: string representing the version of the prebuilt that has been
146 uploaded.
147 retries: The number of times to retry before giving up, default: 5
148 key: The variable key to update in the git file.
149 (Default: PORTAGE_BINHOST)
150 """
151 prebuilt_branch = 'prebuilt_branch'
David James1b6e67a2011-05-19 21:32:38 -0700152 cwd = os.path.abspath(os.path.dirname(filename))
153 commit = cros_build_lib.RunCommand(['git', 'rev-parse', 'HEAD'], cwd=cwd,
Peter Mayofe0e6872011-04-20 00:52:08 -0400154 redirect_stdout=True).output.rstrip()
David James8c846492011-01-25 17:07:29 -0800155 description = 'Update %s="%s" in %s' % (key, value, filename)
156 print description
David James66009462012-03-25 10:08:38 -0700157
David James8c846492011-01-25 17:07:29 -0800158 try:
David James66009462012-03-25 10:08:38 -0700159 cros_build_lib.CreatePushBranch(prebuilt_branch, cwd)
David James8c846492011-01-25 17:07:29 -0800160 UpdateLocalFile(filename, value, key)
David James1b6e67a2011-05-19 21:32:38 -0700161 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
162 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James66009462012-03-25 10:08:38 -0700163 cros_build_lib.GitPushWithRetry(prebuilt_branch, cwd=cwd, dryrun=dryrun,
164 retries=retries)
David James8c846492011-01-25 17:07:29 -0800165 finally:
David James1b6e67a2011-05-19 21:32:38 -0700166 cros_build_lib.RunCommand(['git', 'checkout', commit], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800167
168
169def GetVersion():
170 """Get the version to put in LATEST and update the git version with."""
171 return datetime.datetime.now().strftime('%d.%m.%y.%H%M%S')
172
173
Peter Mayo193f68f2011-04-19 19:08:21 -0400174def _RetryRun(cmd, print_cmd=True, cwd=None):
David James8c846492011-01-25 17:07:29 -0800175 """Run the specified command, retrying if necessary.
176
177 Args:
178 cmd: The command to run.
179 print_cmd: Whether to print out the cmd.
180 shell: Whether to treat the command as a shell.
181 cwd: Working directory to run command in.
182
183 Returns:
184 True if the command succeeded. Otherwise, returns False.
185 """
186
187 # TODO(scottz): port to use _Run or similar when it is available in
188 # cros_build_lib.
Chris Sosa58669192011-06-30 12:45:03 -0700189 for unused_attempt in range(_RETRIES):
David James8c846492011-01-25 17:07:29 -0800190 try:
Chris Sosa58669192011-06-30 12:45:03 -0700191 cros_build_lib.RunCommand(cmd, print_cmd=print_cmd, cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800192 return True
193 except cros_build_lib.RunCommandError:
Peter Mayo193f68f2011-04-19 19:08:21 -0400194 print 'Failed to run %r' % cmd
David James8c846492011-01-25 17:07:29 -0800195 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400196 print 'Retry failed run %r, giving up' % cmd
David James8c846492011-01-25 17:07:29 -0800197 return False
198
199
200def _GsUpload(args):
201 """Upload to GS bucket.
202
203 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800204 args: a tuple of three arguments that contains local_file, remote_file, and
205 the acl used for uploading the file.
David James8c846492011-01-25 17:07:29 -0800206
207 Returns:
208 Return the arg tuple of two if the upload failed
209 """
David Jamesfd0b0852011-02-23 11:15:36 -0800210 (local_file, remote_file, acl) = args
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700211 CANNED_ACLS = ['public-read', 'private', 'bucket-owner-read',
212 'authenticated-read', 'bucket-owner-full-control',
213 'public-read-write']
214 acl_cmd = None
215 if acl in CANNED_ACLS:
Peter Mayo193f68f2011-04-19 19:08:21 -0400216 cmd = [_GSUTIL_BIN, 'cp', '-a', acl, local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700217 else:
218 # For private uploads we assume that the overlay board is set up properly
219 # and a googlestore_acl.xml is present, if not this script errors
Peter Mayo193f68f2011-04-19 19:08:21 -0400220 cmd = [_GSUTIL_BIN, 'cp', '-a', 'private', local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700221 if not os.path.exists(acl):
222 print >> sys.stderr, ('You are specifying either a file that does not '
223 'exist or an unknown canned acl: %s. Aborting '
224 'upload') % acl
225 # emulate the failing of an upload since we are not uploading the file
226 return (local_file, remote_file)
David James8c846492011-01-25 17:07:29 -0800227
Peter Mayo193f68f2011-04-19 19:08:21 -0400228 acl_cmd = [_GSUTIL_BIN, 'setacl', acl, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700229
Peter Mayo193f68f2011-04-19 19:08:21 -0400230 if not _RetryRun(cmd, print_cmd=False):
David James8c846492011-01-25 17:07:29 -0800231 return (local_file, remote_file)
232
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700233 if acl_cmd:
234 # Apply the passed in ACL xml file to the uploaded object.
Peter Mayo193f68f2011-04-19 19:08:21 -0400235 _RetryRun(acl_cmd, print_cmd=False)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700236
237
David Jamesfd0b0852011-02-23 11:15:36 -0800238def RemoteUpload(acl, files, pool=10):
David James8c846492011-01-25 17:07:29 -0800239 """Upload to google storage.
240
241 Create a pool of process and call _GsUpload with the proper arguments.
242
243 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800244 acl: The canned acl used for uploading. acl can be one of: "public-read",
245 "public-read-write", "authenticated-read", "bucket-owner-read",
246 "bucket-owner-full-control", or "private".
David James8c846492011-01-25 17:07:29 -0800247 files: dictionary with keys to local files and values to remote path.
248 pool: integer of maximum proesses to have at the same time.
249
250 Returns:
251 Return a set of tuple arguments of the failed uploads
252 """
253 # TODO(scottz) port this to use _RunManyParallel when it is available in
254 # cros_build_lib
255 pool = multiprocessing.Pool(processes=pool)
256 workers = []
257 for local_file, remote_path in files.iteritems():
David Jamesfd0b0852011-02-23 11:15:36 -0800258 workers.append((local_file, remote_path, acl))
David James8c846492011-01-25 17:07:29 -0800259
260 result = pool.map_async(_GsUpload, workers, chunksize=1)
261 while True:
262 try:
Chris Sosa471532a2011-02-01 15:10:06 -0800263 return set(result.get(60 * 60))
David James8c846492011-01-25 17:07:29 -0800264 except multiprocessing.TimeoutError:
265 pass
266
267
268def GenerateUploadDict(base_local_path, base_remote_path, pkgs):
269 """Build a dictionary of local remote file key pairs to upload.
270
271 Args:
272 base_local_path: The base path to the files on the local hard drive.
273 remote_path: The base path to the remote paths.
274 pkgs: The packages to upload.
275
276 Returns:
277 Returns a dictionary of local_path/remote_path pairs
278 """
279 upload_files = {}
280 for pkg in pkgs:
281 suffix = pkg['CPV'] + '.tbz2'
282 local_path = os.path.join(base_local_path, suffix)
283 assert os.path.exists(local_path)
284 remote_path = '%s/%s' % (base_remote_path.rstrip('/'), suffix)
285 upload_files[local_path] = remote_path
286
287 return upload_files
288
289def GetBoardPathFromCrosOverlayList(build_path, target):
290 """Use the cros_overlay_list to determine the path to the board overlay
291 Args:
292 build_path: The path to the root of the build directory
293 target: The target that we are looking for, could consist of board and
294 board_variant, we handle that properly
295 Returns:
296 The last line from cros_overlay_list as a string
297 """
Chris Sosa471532a2011-02-01 15:10:06 -0800298 script_dir = os.path.join(build_path, 'src/platform/dev/host')
David James4058b0d2011-12-08 21:24:50 -0800299 cmd = ['./cros_overlay_list', '--board', target.board]
300 if target.variant:
301 cmd += ['--variant', target.variant]
David James8c846492011-01-25 17:07:29 -0800302
303 cmd_output = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
304 cwd=script_dir)
305 # We only care about the last entry
306 return cmd_output.output.splitlines().pop()
307
308
309def DeterminePrebuiltConfFile(build_path, target):
310 """Determine the prebuilt.conf file that needs to be updated for prebuilts.
311
312 Args:
313 build_path: The path to the root of the build directory
314 target: String representation of the board. This includes host and board
315 targets
316
317 Returns
318 A string path to a prebuilt.conf file to be updated.
319 """
David James4058b0d2011-12-08 21:24:50 -0800320 if _HOST_ARCH == target:
David James8c846492011-01-25 17:07:29 -0800321 # We are host.
322 # Without more examples of hosts this is a kludge for now.
323 # TODO(Scottz): as new host targets come online expand this to
324 # work more like boards.
Chris Sosa471532a2011-02-01 15:10:06 -0800325 make_path = _PREBUILT_MAKE_CONF[target]
David James8c846492011-01-25 17:07:29 -0800326 else:
327 # We are a board
328 board = GetBoardPathFromCrosOverlayList(build_path, target)
329 make_path = os.path.join(board, 'prebuilt.conf')
330
331 return make_path
332
333
334def UpdateBinhostConfFile(path, key, value):
335 """Update binhost config file file with key=value.
336
337 Args:
338 path: Filename to update.
339 key: Key to update.
340 value: New value for key.
341 """
342 cwd = os.path.dirname(os.path.abspath(path))
343 filename = os.path.basename(path)
344 if not os.path.isdir(cwd):
345 os.makedirs(cwd)
346 if not os.path.isfile(path):
347 config_file = file(path, 'w')
David James8c846492011-01-25 17:07:29 -0800348 config_file.close()
349 UpdateLocalFile(path, value, key)
Chris Sosac13bba52011-05-24 15:14:09 -0700350 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
David James20b2b6f2011-11-18 15:11:58 -0800351 description = 'Update %s="%s" in %s' % (key, value, filename)
Peter Mayo193f68f2011-04-19 19:08:21 -0400352 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800353
354
David Jamesce093af2011-02-23 15:21:58 -0800355def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800356 """Grab all of the packages files associated with a list of binhost_urls.
357
David James05bcb2b2011-02-09 09:25:47 -0800358 Args:
359 binhost_urls: The URLs for the directories containing the Packages files we
360 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800361
362 Returns:
363 A list of PackageIndex objects.
364 """
365 pkg_indexes = []
366 for url in binhost_urls:
367 pkg_index = GrabRemotePackageIndex(url)
368 if pkg_index:
369 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800370 return pkg_indexes
371
372
David James05bcb2b2011-02-09 09:25:47 -0800373
David Jamesc0f158a2011-02-22 16:07:29 -0800374class PrebuiltUploader(object):
375 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800376
David James615e5b52011-06-03 11:10:15 -0700377 def __init__(self, upload_location, acl, binhost_base_url,
David James32b0b2f2011-07-13 20:56:50 -0700378 pkg_indexes, build_path, packages, skip_upload,
David James4058b0d2011-12-08 21:24:50 -0800379 binhost_conf_dir, debug, target, slave_targets):
David Jamesc0f158a2011-02-22 16:07:29 -0800380 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800381
David Jamesc0f158a2011-02-22 16:07:29 -0800382 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800383
David Jamesc0f158a2011-02-22 16:07:29 -0800384 Args:
385 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800386 acl: The canned acl used for uploading to Google Storage. acl can be one
387 of: "public-read", "public-read-write", "authenticated-read",
388 "bucket-owner-read", "bucket-owner-full-control", or "private". If
389 we are not uploading to Google Storage, this parameter is unused.
390 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800391 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
392 uploading duplicate files, we just link to the old files.
David James615e5b52011-06-03 11:10:15 -0700393 build_path: The path to the directory containing the chroot.
394 packages: Packages to upload.
David James32b0b2f2011-07-13 20:56:50 -0700395 skip_upload: Don't actually upload the tarballs.
396 binhost_conf_dir: Directory where to store binhost.conf files.
397 debug: Don't push or upload prebuilts.
David James4058b0d2011-12-08 21:24:50 -0800398 target: BuildTarget managed by this builder.
399 slave_targets: List of BuildTargets managed by slave builders.
David Jamesc0f158a2011-02-22 16:07:29 -0800400 """
401 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800402 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800403 self._binhost_base_url = binhost_base_url
404 self._pkg_indexes = pkg_indexes
David James615e5b52011-06-03 11:10:15 -0700405 self._build_path = build_path
406 self._packages = set(packages)
David James8ece7ee2011-06-29 16:02:30 -0700407 self._skip_upload = skip_upload
David James32b0b2f2011-07-13 20:56:50 -0700408 self._binhost_conf_dir = binhost_conf_dir
David James27fa7d12011-06-29 17:24:14 -0700409 self._debug = debug
David James4058b0d2011-12-08 21:24:50 -0800410 self._target = target
411 self._slave_targets = slave_targets
David James615e5b52011-06-03 11:10:15 -0700412
413 def _ShouldFilterPackage(self, pkg):
414 if not self._packages:
415 return False
416 pym_path = os.path.abspath(os.path.join(self._build_path, _PYM_PATH))
David James710b7dc2012-02-07 16:49:59 -0800417 sys.path.insert(0, pym_path)
David James615e5b52011-06-03 11:10:15 -0700418 import portage.versions
419 cat, pkgname = portage.versions.catpkgsplit(pkg['CPV'])[0:2]
420 cp = '%s/%s' % (cat, pkgname)
421 return pkgname not in self._packages and cp not in self._packages
David James8c846492011-01-25 17:07:29 -0800422
David Jamesc0f158a2011-02-22 16:07:29 -0800423 def _UploadPrebuilt(self, package_path, url_suffix):
424 """Upload host or board prebuilt files to Google Storage space.
David James8c846492011-01-25 17:07:29 -0800425
David Jamesc0f158a2011-02-22 16:07:29 -0800426 Args:
427 package_path: The path to the packages dir.
David Jamesce093af2011-02-23 15:21:58 -0800428 url_suffix: The remote subdirectory where we should upload the packages.
David Jamesa3bba142011-05-26 21:24:20 -0700429
David Jamesc0f158a2011-02-22 16:07:29 -0800430 """
David James8c846492011-01-25 17:07:29 -0800431
David Jamesc0f158a2011-02-22 16:07:29 -0800432 # Process Packages file, removing duplicates and filtered packages.
433 pkg_index = GrabLocalPackageIndex(package_path)
434 pkg_index.SetUploadLocation(self._binhost_base_url, url_suffix)
David James615e5b52011-06-03 11:10:15 -0700435 pkg_index.RemoveFilteredPackages(self._ShouldFilterPackage)
David Jamesc0f158a2011-02-22 16:07:29 -0800436 uploads = pkg_index.ResolveDuplicateUploads(self._pkg_indexes)
David James05bcb2b2011-02-09 09:25:47 -0800437
David Jamesc0f158a2011-02-22 16:07:29 -0800438 # Write Packages file.
439 tmp_packages_file = pkg_index.WriteToNamedTemporaryFile()
David James05bcb2b2011-02-09 09:25:47 -0800440
David Jamesc0f158a2011-02-22 16:07:29 -0800441 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
442 if remote_location.startswith('gs://'):
443 # Build list of files to upload.
444 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
445 remote_file = '%s/Packages' % remote_location.rstrip('/')
446 upload_files[tmp_packages_file.name] = remote_file
David James05bcb2b2011-02-09 09:25:47 -0800447
David Jamesfd0b0852011-02-23 11:15:36 -0800448 failed_uploads = RemoteUpload(self._acl, upload_files)
David Jamesc0f158a2011-02-22 16:07:29 -0800449 if len(failed_uploads) > 1 or (None not in failed_uploads):
David Jamesf4db1122011-03-17 16:18:05 -0700450 error_msg = ['%s -> %s\n' % args for args in failed_uploads if args]
David Jamesc0f158a2011-02-22 16:07:29 -0800451 raise UploadFailed('Error uploading:\n%s' % error_msg)
452 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400453 pkgs = [p['CPV'] + '.tbz2' for p in uploads]
David Jamesc0f158a2011-02-22 16:07:29 -0800454 ssh_server, remote_path = remote_location.split(':', 1)
Peter Mayo193f68f2011-04-19 19:08:21 -0400455 remote_path = remote_path.rstrip('/')
456 pkg_index = tmp_packages_file.name
457 remote_location = remote_location.rstrip('/')
458 remote_packages = '%s/Packages' % remote_location
459 cmds = [['ssh', ssh_server, 'mkdir', '-p', remote_path],
460 ['rsync', '-av', '--chmod=a+r', pkg_index, remote_packages]]
David Jamesc0f158a2011-02-22 16:07:29 -0800461 if pkgs:
Peter Mayo193f68f2011-04-19 19:08:21 -0400462 cmds.append(['rsync', '-Rav'] + pkgs + [remote_location + '/'])
David Jamesc0f158a2011-02-22 16:07:29 -0800463 for cmd in cmds:
Peter Mayo193f68f2011-04-19 19:08:21 -0400464 if not _RetryRun(cmd, cwd=package_path):
465 raise UploadFailed('Could not run %r' % cmd)
David James8c846492011-01-25 17:07:29 -0800466
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200467 def _UploadBoardTarball(self, board_path, url_suffix, version):
David James8fa34ea2011-04-15 13:00:20 -0700468 """Upload a tarball of the board at the specified path to Google Storage.
469
470 Args:
471 board_path: The path to the board dir.
472 url_suffix: The remote subdirectory where we should upload the packages.
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200473 version: The version of the board.
David James8fa34ea2011-04-15 13:00:20 -0700474 """
475 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
476 assert remote_location.startswith('gs://')
477 cwd, boardname = os.path.split(board_path.rstrip(os.path.sep))
478 tmpdir = tempfile.mkdtemp()
479 try:
480 tarfile = os.path.join(tmpdir, '%s.tbz2' % boardname)
Brian Harringd223a242012-02-03 20:12:10 -0800481 cmd = ['tar', '-I', 'pbzip2', '-cf', tarfile]
David James8fa34ea2011-04-15 13:00:20 -0700482 excluded_paths = ('usr/lib/debug', 'usr/local/autotest', 'packages',
483 'tmp')
484 for path in excluded_paths:
Zdenek Behane3ed3462011-06-16 00:36:08 +0200485 cmd.append('--exclude=%s/*' % path)
486 cmd.append('.')
Brian Harringd223a242012-02-03 20:12:10 -0800487 cros_build_lib.SudoRunCommand(cmd, cwd=os.path.join(cwd, boardname))
David James8fa34ea2011-04-15 13:00:20 -0700488 remote_tarfile = '%s/%s.tbz2' % (remote_location.rstrip('/'), boardname)
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200489 # FIXME(zbehan): Temporary hack to upload amd64-host chroots to a
490 # different gs bucket. The right way is to do the upload in a separate
491 # pass of this script.
492 if boardname == 'amd64-host':
Zdenek Behanaf3c9002011-06-24 10:07:40 +0200493 # FIXME(zbehan): Why does version contain the prefix "chroot-"?
494 remote_tarfile = \
495 'gs://chromiumos-sdk/cros-sdk-%s.tbz2' % version.strip('chroot-')
David James8fa34ea2011-04-15 13:00:20 -0700496 if _GsUpload((tarfile, remote_tarfile, self._acl)):
497 sys.exit(1)
498 finally:
Brian Harringd223a242012-02-03 20:12:10 -0800499 cros_build_lib.SudoRunCommand(['rm', '-rf', tmpdir], cwd=cwd)
David James8fa34ea2011-04-15 13:00:20 -0700500
David James615e5b52011-06-03 11:10:15 -0700501 def _SyncHostPrebuilts(self, version, key, git_sync, sync_binhost_conf):
David Jamesc0f158a2011-02-22 16:07:29 -0800502 """Synchronize host prebuilt files.
David James05bcb2b2011-02-09 09:25:47 -0800503
David Jamesc0f158a2011-02-22 16:07:29 -0800504 This function will sync both the standard host packages, plus the host
505 packages associated with all targets that have been "setup" with the
506 current host's chroot. For instance, if this host has been used to build
507 x86-generic, it will sync the host packages associated with
508 'i686-pc-linux-gnu'. If this host has also been used to build arm-generic,
509 it will also sync the host packages associated with
510 'armv7a-cros-linux-gnueabi'.
David James05bcb2b2011-02-09 09:25:47 -0800511
David Jamesc0f158a2011-02-22 16:07:29 -0800512 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800513 version: A unique string, intended to be included in the upload path,
514 which identifies the version number of the uploaded prebuilts.
515 key: The variable key to update in the git file.
516 git_sync: If set, update make.conf of target to reference the latest
517 prebuilt packages generated here.
518 sync_binhost_conf: If set, update binhost config file in
519 chromiumos-overlay for the host.
520 """
David Jamese2488642011-11-14 16:15:20 -0800521 # Slave boards are listed before the master board so that the master board
522 # takes priority (i.e. x86-generic preflight host prebuilts takes priority
523 # over preflight host prebuilts from other builders.)
524 binhost_urls = []
David James4058b0d2011-12-08 21:24:50 -0800525 for target in self._slave_targets + [self._target]:
526 url_suffix = _REL_HOST_PATH % {'version': version,
527 'host_arch': _HOST_ARCH,
528 'target': target}
David Jamese2488642011-11-14 16:15:20 -0800529 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James05bcb2b2011-02-09 09:25:47 -0800530
David James4058b0d2011-12-08 21:24:50 -0800531 if self._target == target and not self._skip_upload and not self._debug:
David Jamese2488642011-11-14 16:15:20 -0800532 # Upload prebuilts.
533 package_path = os.path.join(self._build_path, _HOST_PACKAGES_PATH)
534 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8ece7ee2011-06-29 16:02:30 -0700535
David Jamese2488642011-11-14 16:15:20 -0800536 # Record URL where prebuilts were uploaded.
537 binhost_urls.append('%s/%s/' % (self._binhost_base_url.rstrip('/'),
538 packages_url_suffix.rstrip('/')))
539
David James20b2b6f2011-11-18 15:11:58 -0800540 binhost = ' '.join(binhost_urls)
David James8ece7ee2011-06-29 16:02:30 -0700541 if git_sync:
542 git_file = os.path.join(self._build_path,
David James4058b0d2011-12-08 21:24:50 -0800543 _PREBUILT_MAKE_CONF[_HOST_ARCH])
David Jamese2488642011-11-14 16:15:20 -0800544 RevGitFile(git_file, binhost, key=key, dryrun=self._debug)
David James8ece7ee2011-06-29 16:02:30 -0700545 if sync_binhost_conf:
David James32b0b2f2011-07-13 20:56:50 -0700546 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James4058b0d2011-12-08 21:24:50 -0800547 'host', '%s-%s.conf' % (_HOST_ARCH, key))
David Jamese2488642011-11-14 16:15:20 -0800548 UpdateBinhostConfFile(binhost_conf, key, binhost)
David Jamesc0f158a2011-02-22 16:07:29 -0800549
David Jamese2488642011-11-14 16:15:20 -0800550 def _SyncBoardPrebuilts(self, version, key, git_sync, sync_binhost_conf,
551 upload_board_tarball):
David Jamesc0f158a2011-02-22 16:07:29 -0800552 """Synchronize board prebuilt files.
553
554 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800555 version: A unique string, intended to be included in the upload path,
556 which identifies the version number of the uploaded prebuilts.
557 key: The variable key to update in the git file.
558 git_sync: If set, update make.conf of target to reference the latest
559 prebuilt packages generated here.
560 sync_binhost_conf: If set, update binhost config file in
561 chromiumos-overlay for the current board.
David James8fa34ea2011-04-15 13:00:20 -0700562 upload_board_tarball: Include a tarball of the board in our upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800563 """
David James4058b0d2011-12-08 21:24:50 -0800564 for target in self._slave_targets + [self._target]:
David Jamese2488642011-11-14 16:15:20 -0800565 board_path = os.path.join(self._build_path,
David James4058b0d2011-12-08 21:24:50 -0800566 _BOARD_PATH % {'board': target.board_variant})
David Jamese2488642011-11-14 16:15:20 -0800567 package_path = os.path.join(board_path, 'packages')
David James4058b0d2011-12-08 21:24:50 -0800568 url_suffix = _REL_BOARD_PATH % {'target': target, 'version': version}
David Jamese2488642011-11-14 16:15:20 -0800569 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James8fa34ea2011-04-15 13:00:20 -0700570
David James4058b0d2011-12-08 21:24:50 -0800571 if self._target == target and not self._skip_upload and not self._debug:
David Jamese2488642011-11-14 16:15:20 -0800572 # Upload board tarballs in the background.
573 if upload_board_tarball:
574 tar_process = multiprocessing.Process(target=self._UploadBoardTarball,
575 args=(board_path, url_suffix,
576 version))
577 tar_process.start()
David James8fa34ea2011-04-15 13:00:20 -0700578
David Jamese2488642011-11-14 16:15:20 -0800579 # Upload prebuilts.
580 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8fa34ea2011-04-15 13:00:20 -0700581
David Jamese2488642011-11-14 16:15:20 -0800582 # Make sure we finished uploading the board tarballs.
583 if upload_board_tarball:
584 tar_process.join()
585 assert tar_process.exitcode == 0
586 # TODO(zbehan): This should be done cleaner.
David James4058b0d2011-12-08 21:24:50 -0800587 if target.board == 'amd64-host':
David Jamese2488642011-11-14 16:15:20 -0800588 sdk_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James8ece7ee2011-06-29 16:02:30 -0700589 'host/sdk_version.conf')
David Jamese2488642011-11-14 16:15:20 -0800590 RevGitFile(sdk_conf, version.strip('chroot-'),
591 key='SDK_LATEST_VERSION', dryrun=self._debug)
David Jamesc0f158a2011-02-22 16:07:29 -0800592
David Jamese2488642011-11-14 16:15:20 -0800593 # Record URL where prebuilts were uploaded.
594 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
595 packages_url_suffix.rstrip('/'))
596
597 if git_sync:
David James4058b0d2011-12-08 21:24:50 -0800598 git_file = DeterminePrebuiltConfFile(self._build_path, target)
David Jamese2488642011-11-14 16:15:20 -0800599 RevGitFile(git_file, url_value, key=key, dryrun=self._debug)
600 if sync_binhost_conf:
601 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James4058b0d2011-12-08 21:24:50 -0800602 'target', '%s-%s.conf' % (target, key))
David Jamese2488642011-11-14 16:15:20 -0800603 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800604
605
David James8c846492011-01-25 17:07:29 -0800606def usage(parser, msg):
607 """Display usage message and parser help then exit with 1."""
608 print >> sys.stderr, msg
609 parser.print_help()
610 sys.exit(1)
611
David James4058b0d2011-12-08 21:24:50 -0800612
613def add_slave_board(_option, _opt_str, value, parser):
614 parser.values.slave_targets.append(BuildTarget(value))
615
616
617def add_slave_profile(_option, _opt_str, value, parser):
618 if not parser.values.slave_targets:
619 usage(parser, 'Must specify --slave-board before --slave-profile')
620 if parser.values.slave_targets[-1].profile is not None:
621 usage(parser, 'Cannot specify --slave-profile twice for same board')
622 parser.values.slave_targets[-1].profile = value
623
624
David Jamesc0f158a2011-02-22 16:07:29 -0800625def ParseOptions():
David James8c846492011-01-25 17:07:29 -0800626 parser = optparse.OptionParser()
627 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
628 default=_BINHOST_BASE_URL,
629 help='Base URL to use for binhost in make.conf updates')
630 parser.add_option('', '--previous-binhost-url', action='append',
631 default=[], dest='previous_binhost_url',
632 help='Previous binhost URL')
633 parser.add_option('-b', '--board', dest='board', default=None,
634 help='Board type that was built on this machine')
David James4058b0d2011-12-08 21:24:50 -0800635 parser.add_option('', '--profile', dest='profile', default=None,
636 help='Profile that was built on this machine')
637 parser.add_option('', '--slave-board', default=[], action='callback',
638 dest='slave_targets', type='string',
639 callback=add_slave_board,
640 help='Board type that was built on a slave machine. To '
641 'add a profile to this board, use --slave-profile.')
642 parser.add_option('', '--slave-profile', action='callback', type='string',
643 callback=add_slave_profile,
644 help='Board profile that was built on a slave machine. '
645 'Applies to previous slave board.')
David James8c846492011-01-25 17:07:29 -0800646 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800647 help='Path to the directory containing the chroot')
David James615e5b52011-06-03 11:10:15 -0700648 parser.add_option('', '--packages', action='append',
649 default=[], dest='packages',
650 help='Only include the specified packages. '
651 '(Default is to include all packages.)')
David James8c846492011-01-25 17:07:29 -0800652 parser.add_option('-s', '--sync-host', dest='sync_host',
653 default=False, action='store_true',
654 help='Sync host prebuilts')
655 parser.add_option('-g', '--git-sync', dest='git_sync',
656 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800657 help='Enable git version sync (This commits to a repo.) '
658 'This is used by full builders to commit directly '
659 'to board overlays.')
David James8c846492011-01-25 17:07:29 -0800660 parser.add_option('-u', '--upload', dest='upload',
661 default=None,
662 help='Upload location')
663 parser.add_option('-V', '--prepend-version', dest='prepend_version',
664 default=None,
665 help='Add an identifier to the front of the version')
666 parser.add_option('-f', '--filters', dest='filters', action='store_true',
667 default=False,
668 help='Turn on filtering of private ebuild packages')
669 parser.add_option('-k', '--key', dest='key',
670 default='PORTAGE_BINHOST',
671 help='Key to update in make.conf / binhost.conf')
David James8ece7ee2011-06-29 16:02:30 -0700672 parser.add_option('', '--set-version', dest='set_version',
673 default=None,
674 help='Specify the version string')
David James8c846492011-01-25 17:07:29 -0800675 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
676 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800677 help='Update binhost.conf in chromiumos-overlay or '
678 'chromeos-overlay. Commit the changes, but don\'t '
679 'push them. This is used for preflight binhosts.')
David James32b0b2f2011-07-13 20:56:50 -0700680 parser.add_option('', '--binhost-conf-dir', dest='binhost_conf_dir',
681 default=_BINHOST_CONF_DIR,
682 help='Directory to commit binhost config with '
683 '--sync-binhost-conf.')
David Jamesfd0b0852011-02-23 11:15:36 -0800684 parser.add_option('-P', '--private', dest='private', action='store_true',
685 default=False, help='Mark gs:// uploads as private.')
David James8ece7ee2011-06-29 16:02:30 -0700686 parser.add_option('', '--skip-upload', dest='skip_upload',
687 action='store_true', default=False,
688 help='Skip upload step.')
David James8fa34ea2011-04-15 13:00:20 -0700689 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
690 action='store_true', default=False,
691 help='Upload board tarball to Google Storage.')
David James27fa7d12011-06-29 17:24:14 -0700692 parser.add_option('', '--debug', dest='debug',
693 action='store_true', default=False,
694 help='Don\'t push or upload prebuilts.')
David James8c846492011-01-25 17:07:29 -0800695
696 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800697 if not options.build_path:
698 usage(parser, 'Error: you need provide a chroot path')
David James8ece7ee2011-06-29 16:02:30 -0700699 if not options.upload and not options.skip_upload:
David James8c846492011-01-25 17:07:29 -0800700 usage(parser, 'Error: you need to provide an upload location using -u')
David James8ece7ee2011-06-29 16:02:30 -0700701 if not options.set_version and options.skip_upload:
702 usage(parser, 'Error: If you are using --skip-upload, you must specify a '
703 'version number using --set-version.')
David James9417f272011-05-26 13:24:47 -0700704 if args:
705 usage(parser, 'Error: invalid arguments passed to prebuilt.py: %r' % args)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700706
David James4058b0d2011-12-08 21:24:50 -0800707 options.target = BuildTarget(options.board, options.profile)
708 if options.target in options.slave_targets:
709 usage(parser, 'Error: --board/--profile must not also be a slave target.')
David Jamese2488642011-11-14 16:15:20 -0800710
David James4058b0d2011-12-08 21:24:50 -0800711 if len(set(options.slave_targets)) != len(options.slave_targets):
David Jamese2488642011-11-14 16:15:20 -0800712 usage(parser, 'Error: --slave-boards must not have duplicates.')
713
David James4058b0d2011-12-08 21:24:50 -0800714 if options.slave_targets and options.git_sync:
David Jamese2488642011-11-14 16:15:20 -0800715 usage(parser, 'Error: --slave-boards is not compatible with --git-sync')
716
David James8ece7ee2011-06-29 16:02:30 -0700717 if (options.upload_board_tarball and options.skip_upload and
718 options.board == 'amd64-host'):
719 usage(parser, 'Error: --skip-upload is not compatible with '
720 '--upload-board-tarball and --board=amd64-host')
David James8fa34ea2011-04-15 13:00:20 -0700721
David James8ece7ee2011-06-29 16:02:30 -0700722 if (options.upload_board_tarball and not options.skip_upload and
723 not options.upload.startswith('gs://')):
David James8fa34ea2011-04-15 13:00:20 -0700724 usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
725 '--upload must be a gs:// URL.')
726
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700727 if options.private:
728 if options.sync_host:
729 usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
730 'together, we do not support private host prebuilts')
731
David James8ece7ee2011-06-29 16:02:30 -0700732 if not options.upload or not options.upload.startswith('gs://'):
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700733 usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
734 '--upload must be a gs:// URL.')
735
736 if options.binhost_base_url != _BINHOST_BASE_URL:
David Jamese2488642011-11-14 16:15:20 -0800737 usage(parser, 'Error: when using --private the --binhost-base-url '
738 'is automatically derived.')
David James27fa7d12011-06-29 17:24:14 -0700739
David Jamesc0f158a2011-02-22 16:07:29 -0800740 return options
741
742def main():
David Jamesdb401072011-06-10 12:17:16 -0700743 # Set umask to a sane value so that files created as root are readable.
744 os.umask(022)
745
David Jamesc0f158a2011-02-22 16:07:29 -0800746 options = ParseOptions()
747
David James05bcb2b2011-02-09 09:25:47 -0800748 # Calculate a list of Packages index files to compare against. Whenever we
749 # upload a package, we check to make sure it's not already stored in one of
750 # the packages files we uploaded. This list of packages files might contain
751 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800752 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800753
David James8ece7ee2011-06-29 16:02:30 -0700754 if options.set_version:
755 version = options.set_version
756 else:
757 version = GetVersion()
David Jamesc0f158a2011-02-22 16:07:29 -0800758 if options.prepend_version:
759 version = '%s-%s' % (options.prepend_version, version)
760
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700761 acl = 'public-read'
762 binhost_base_url = options.binhost_base_url
763
764 if options.private:
765 binhost_base_url = options.upload
766 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
David James4058b0d2011-12-08 21:24:50 -0800767 options.target)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700768 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
769
770 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David James615e5b52011-06-03 11:10:15 -0700771 pkg_indexes, options.build_path,
David James27fa7d12011-06-29 17:24:14 -0700772 options.packages, options.skip_upload,
David Jamese2488642011-11-14 16:15:20 -0800773 options.binhost_conf_dir, options.debug,
David James4058b0d2011-12-08 21:24:50 -0800774 options.target, options.slave_targets)
David Jamesc0f158a2011-02-22 16:07:29 -0800775
David James8c846492011-01-25 17:07:29 -0800776 if options.sync_host:
David James615e5b52011-06-03 11:10:15 -0700777 uploader._SyncHostPrebuilts(version, options.key, options.git_sync,
778 options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800779
780 if options.board:
David Jamese2488642011-11-14 16:15:20 -0800781 uploader._SyncBoardPrebuilts(version, options.key, options.git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700782 options.sync_binhost_conf,
783 options.upload_board_tarball)
David James8c846492011-01-25 17:07:29 -0800784
785if __name__ == '__main__':
786 main()