blob: c15949168c401935f4f1d1e3b834b4e94214d31f [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
15 sys.path.append(constants.SOURCE_ROOT)
16
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()
Brian Harring1aefee02011-12-21 20:33:48 -0800155
156 # We want to push our changes to this file to tip of tree, thus force
157 # remotes to update; repo start always starts from HEAD of the manifest
158 # defined branch thus no need to do a reset.
David Jamesbcfdc5d2011-08-25 20:46:33 -0700159 _RetryRun(['git', 'remote', 'update'], cwd=cwd)
David James1b6e67a2011-05-19 21:32:38 -0700160 cros_build_lib.RunCommand(['repo', 'start', prebuilt_branch, '.'], cwd=cwd)
David James6181b892011-06-08 16:45:07 -0700161
David James8c846492011-01-25 17:07:29 -0800162 description = 'Update %s="%s" in %s' % (key, value, filename)
163 print description
164 try:
165 UpdateLocalFile(filename, value, key)
David James1b6e67a2011-05-19 21:32:38 -0700166 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
167 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James27fa7d12011-06-29 17:24:14 -0700168 cros_build_lib.GitPushWithRetry(prebuilt_branch, cwd=cwd, dryrun=dryrun)
David James8c846492011-01-25 17:07:29 -0800169 finally:
David James1b6e67a2011-05-19 21:32:38 -0700170 cros_build_lib.RunCommand(['git', 'checkout', commit], cwd=cwd)
171 cros_build_lib.RunCommand(['repo', 'abandon', 'prebuilt_branch', '.'],
172 cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800173
174
175def GetVersion():
176 """Get the version to put in LATEST and update the git version with."""
177 return datetime.datetime.now().strftime('%d.%m.%y.%H%M%S')
178
179
Peter Mayo193f68f2011-04-19 19:08:21 -0400180def _RetryRun(cmd, print_cmd=True, cwd=None):
David James8c846492011-01-25 17:07:29 -0800181 """Run the specified command, retrying if necessary.
182
183 Args:
184 cmd: The command to run.
185 print_cmd: Whether to print out the cmd.
186 shell: Whether to treat the command as a shell.
187 cwd: Working directory to run command in.
188
189 Returns:
190 True if the command succeeded. Otherwise, returns False.
191 """
192
193 # TODO(scottz): port to use _Run or similar when it is available in
194 # cros_build_lib.
Chris Sosa58669192011-06-30 12:45:03 -0700195 for unused_attempt in range(_RETRIES):
David James8c846492011-01-25 17:07:29 -0800196 try:
Chris Sosa58669192011-06-30 12:45:03 -0700197 cros_build_lib.RunCommand(cmd, print_cmd=print_cmd, cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800198 return True
199 except cros_build_lib.RunCommandError:
Peter Mayo193f68f2011-04-19 19:08:21 -0400200 print 'Failed to run %r' % cmd
David James8c846492011-01-25 17:07:29 -0800201 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400202 print 'Retry failed run %r, giving up' % cmd
David James8c846492011-01-25 17:07:29 -0800203 return False
204
205
206def _GsUpload(args):
207 """Upload to GS bucket.
208
209 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800210 args: a tuple of three arguments that contains local_file, remote_file, and
211 the acl used for uploading the file.
David James8c846492011-01-25 17:07:29 -0800212
213 Returns:
214 Return the arg tuple of two if the upload failed
215 """
David Jamesfd0b0852011-02-23 11:15:36 -0800216 (local_file, remote_file, acl) = args
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700217 CANNED_ACLS = ['public-read', 'private', 'bucket-owner-read',
218 'authenticated-read', 'bucket-owner-full-control',
219 'public-read-write']
220 acl_cmd = None
221 if acl in CANNED_ACLS:
Peter Mayo193f68f2011-04-19 19:08:21 -0400222 cmd = [_GSUTIL_BIN, 'cp', '-a', acl, local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700223 else:
224 # For private uploads we assume that the overlay board is set up properly
225 # and a googlestore_acl.xml is present, if not this script errors
Peter Mayo193f68f2011-04-19 19:08:21 -0400226 cmd = [_GSUTIL_BIN, 'cp', '-a', 'private', local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700227 if not os.path.exists(acl):
228 print >> sys.stderr, ('You are specifying either a file that does not '
229 'exist or an unknown canned acl: %s. Aborting '
230 'upload') % acl
231 # emulate the failing of an upload since we are not uploading the file
232 return (local_file, remote_file)
David James8c846492011-01-25 17:07:29 -0800233
Peter Mayo193f68f2011-04-19 19:08:21 -0400234 acl_cmd = [_GSUTIL_BIN, 'setacl', acl, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700235
Peter Mayo193f68f2011-04-19 19:08:21 -0400236 if not _RetryRun(cmd, print_cmd=False):
David James8c846492011-01-25 17:07:29 -0800237 return (local_file, remote_file)
238
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700239 if acl_cmd:
240 # Apply the passed in ACL xml file to the uploaded object.
Peter Mayo193f68f2011-04-19 19:08:21 -0400241 _RetryRun(acl_cmd, print_cmd=False)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700242
243
David Jamesfd0b0852011-02-23 11:15:36 -0800244def RemoteUpload(acl, files, pool=10):
David James8c846492011-01-25 17:07:29 -0800245 """Upload to google storage.
246
247 Create a pool of process and call _GsUpload with the proper arguments.
248
249 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800250 acl: The canned acl used for uploading. acl can be one of: "public-read",
251 "public-read-write", "authenticated-read", "bucket-owner-read",
252 "bucket-owner-full-control", or "private".
David James8c846492011-01-25 17:07:29 -0800253 files: dictionary with keys to local files and values to remote path.
254 pool: integer of maximum proesses to have at the same time.
255
256 Returns:
257 Return a set of tuple arguments of the failed uploads
258 """
259 # TODO(scottz) port this to use _RunManyParallel when it is available in
260 # cros_build_lib
261 pool = multiprocessing.Pool(processes=pool)
262 workers = []
263 for local_file, remote_path in files.iteritems():
David Jamesfd0b0852011-02-23 11:15:36 -0800264 workers.append((local_file, remote_path, acl))
David James8c846492011-01-25 17:07:29 -0800265
266 result = pool.map_async(_GsUpload, workers, chunksize=1)
267 while True:
268 try:
Chris Sosa471532a2011-02-01 15:10:06 -0800269 return set(result.get(60 * 60))
David James8c846492011-01-25 17:07:29 -0800270 except multiprocessing.TimeoutError:
271 pass
272
273
274def GenerateUploadDict(base_local_path, base_remote_path, pkgs):
275 """Build a dictionary of local remote file key pairs to upload.
276
277 Args:
278 base_local_path: The base path to the files on the local hard drive.
279 remote_path: The base path to the remote paths.
280 pkgs: The packages to upload.
281
282 Returns:
283 Returns a dictionary of local_path/remote_path pairs
284 """
285 upload_files = {}
286 for pkg in pkgs:
287 suffix = pkg['CPV'] + '.tbz2'
288 local_path = os.path.join(base_local_path, suffix)
289 assert os.path.exists(local_path)
290 remote_path = '%s/%s' % (base_remote_path.rstrip('/'), suffix)
291 upload_files[local_path] = remote_path
292
293 return upload_files
294
295def GetBoardPathFromCrosOverlayList(build_path, target):
296 """Use the cros_overlay_list to determine the path to the board overlay
297 Args:
298 build_path: The path to the root of the build directory
299 target: The target that we are looking for, could consist of board and
300 board_variant, we handle that properly
301 Returns:
302 The last line from cros_overlay_list as a string
303 """
Chris Sosa471532a2011-02-01 15:10:06 -0800304 script_dir = os.path.join(build_path, 'src/platform/dev/host')
David James4058b0d2011-12-08 21:24:50 -0800305 cmd = ['./cros_overlay_list', '--board', target.board]
306 if target.variant:
307 cmd += ['--variant', target.variant]
David James8c846492011-01-25 17:07:29 -0800308
309 cmd_output = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
310 cwd=script_dir)
311 # We only care about the last entry
312 return cmd_output.output.splitlines().pop()
313
314
315def DeterminePrebuiltConfFile(build_path, target):
316 """Determine the prebuilt.conf file that needs to be updated for prebuilts.
317
318 Args:
319 build_path: The path to the root of the build directory
320 target: String representation of the board. This includes host and board
321 targets
322
323 Returns
324 A string path to a prebuilt.conf file to be updated.
325 """
David James4058b0d2011-12-08 21:24:50 -0800326 if _HOST_ARCH == target:
David James8c846492011-01-25 17:07:29 -0800327 # We are host.
328 # Without more examples of hosts this is a kludge for now.
329 # TODO(Scottz): as new host targets come online expand this to
330 # work more like boards.
Chris Sosa471532a2011-02-01 15:10:06 -0800331 make_path = _PREBUILT_MAKE_CONF[target]
David James8c846492011-01-25 17:07:29 -0800332 else:
333 # We are a board
334 board = GetBoardPathFromCrosOverlayList(build_path, target)
335 make_path = os.path.join(board, 'prebuilt.conf')
336
337 return make_path
338
339
340def UpdateBinhostConfFile(path, key, value):
341 """Update binhost config file file with key=value.
342
343 Args:
344 path: Filename to update.
345 key: Key to update.
346 value: New value for key.
347 """
348 cwd = os.path.dirname(os.path.abspath(path))
349 filename = os.path.basename(path)
350 if not os.path.isdir(cwd):
351 os.makedirs(cwd)
352 if not os.path.isfile(path):
353 config_file = file(path, 'w')
David James8c846492011-01-25 17:07:29 -0800354 config_file.close()
355 UpdateLocalFile(path, value, key)
Chris Sosac13bba52011-05-24 15:14:09 -0700356 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
David James20b2b6f2011-11-18 15:11:58 -0800357 description = 'Update %s="%s" in %s' % (key, value, filename)
Peter Mayo193f68f2011-04-19 19:08:21 -0400358 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800359
360
David Jamesce093af2011-02-23 15:21:58 -0800361def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800362 """Grab all of the packages files associated with a list of binhost_urls.
363
David James05bcb2b2011-02-09 09:25:47 -0800364 Args:
365 binhost_urls: The URLs for the directories containing the Packages files we
366 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800367
368 Returns:
369 A list of PackageIndex objects.
370 """
371 pkg_indexes = []
372 for url in binhost_urls:
373 pkg_index = GrabRemotePackageIndex(url)
374 if pkg_index:
375 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800376 return pkg_indexes
377
378
David James05bcb2b2011-02-09 09:25:47 -0800379
David Jamesc0f158a2011-02-22 16:07:29 -0800380class PrebuiltUploader(object):
381 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800382
David James615e5b52011-06-03 11:10:15 -0700383 def __init__(self, upload_location, acl, binhost_base_url,
David James32b0b2f2011-07-13 20:56:50 -0700384 pkg_indexes, build_path, packages, skip_upload,
David James4058b0d2011-12-08 21:24:50 -0800385 binhost_conf_dir, debug, target, slave_targets):
David Jamesc0f158a2011-02-22 16:07:29 -0800386 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800387
David Jamesc0f158a2011-02-22 16:07:29 -0800388 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800389
David Jamesc0f158a2011-02-22 16:07:29 -0800390 Args:
391 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800392 acl: The canned acl used for uploading to Google Storage. acl can be one
393 of: "public-read", "public-read-write", "authenticated-read",
394 "bucket-owner-read", "bucket-owner-full-control", or "private". If
395 we are not uploading to Google Storage, this parameter is unused.
396 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800397 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
398 uploading duplicate files, we just link to the old files.
David James615e5b52011-06-03 11:10:15 -0700399 build_path: The path to the directory containing the chroot.
400 packages: Packages to upload.
David James32b0b2f2011-07-13 20:56:50 -0700401 skip_upload: Don't actually upload the tarballs.
402 binhost_conf_dir: Directory where to store binhost.conf files.
403 debug: Don't push or upload prebuilts.
David James4058b0d2011-12-08 21:24:50 -0800404 target: BuildTarget managed by this builder.
405 slave_targets: List of BuildTargets managed by slave builders.
David Jamesc0f158a2011-02-22 16:07:29 -0800406 """
407 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800408 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800409 self._binhost_base_url = binhost_base_url
410 self._pkg_indexes = pkg_indexes
David James615e5b52011-06-03 11:10:15 -0700411 self._build_path = build_path
412 self._packages = set(packages)
David James8ece7ee2011-06-29 16:02:30 -0700413 self._skip_upload = skip_upload
David James32b0b2f2011-07-13 20:56:50 -0700414 self._binhost_conf_dir = binhost_conf_dir
David James27fa7d12011-06-29 17:24:14 -0700415 self._debug = debug
David James4058b0d2011-12-08 21:24:50 -0800416 self._target = target
417 self._slave_targets = slave_targets
David James615e5b52011-06-03 11:10:15 -0700418
419 def _ShouldFilterPackage(self, pkg):
420 if not self._packages:
421 return False
422 pym_path = os.path.abspath(os.path.join(self._build_path, _PYM_PATH))
423 sys.path.append(pym_path)
424 import portage.versions
425 cat, pkgname = portage.versions.catpkgsplit(pkg['CPV'])[0:2]
426 cp = '%s/%s' % (cat, pkgname)
427 return pkgname not in self._packages and cp not in self._packages
David James8c846492011-01-25 17:07:29 -0800428
David Jamesc0f158a2011-02-22 16:07:29 -0800429 def _UploadPrebuilt(self, package_path, url_suffix):
430 """Upload host or board prebuilt files to Google Storage space.
David James8c846492011-01-25 17:07:29 -0800431
David Jamesc0f158a2011-02-22 16:07:29 -0800432 Args:
433 package_path: The path to the packages dir.
David Jamesce093af2011-02-23 15:21:58 -0800434 url_suffix: The remote subdirectory where we should upload the packages.
David Jamesa3bba142011-05-26 21:24:20 -0700435
David Jamesc0f158a2011-02-22 16:07:29 -0800436 """
David James8c846492011-01-25 17:07:29 -0800437
David Jamesc0f158a2011-02-22 16:07:29 -0800438 # Process Packages file, removing duplicates and filtered packages.
439 pkg_index = GrabLocalPackageIndex(package_path)
440 pkg_index.SetUploadLocation(self._binhost_base_url, url_suffix)
David James615e5b52011-06-03 11:10:15 -0700441 pkg_index.RemoveFilteredPackages(self._ShouldFilterPackage)
David Jamesc0f158a2011-02-22 16:07:29 -0800442 uploads = pkg_index.ResolveDuplicateUploads(self._pkg_indexes)
David James05bcb2b2011-02-09 09:25:47 -0800443
David Jamesc0f158a2011-02-22 16:07:29 -0800444 # Write Packages file.
445 tmp_packages_file = pkg_index.WriteToNamedTemporaryFile()
David James05bcb2b2011-02-09 09:25:47 -0800446
David Jamesc0f158a2011-02-22 16:07:29 -0800447 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
448 if remote_location.startswith('gs://'):
449 # Build list of files to upload.
450 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
451 remote_file = '%s/Packages' % remote_location.rstrip('/')
452 upload_files[tmp_packages_file.name] = remote_file
David James05bcb2b2011-02-09 09:25:47 -0800453
David Jamesfd0b0852011-02-23 11:15:36 -0800454 failed_uploads = RemoteUpload(self._acl, upload_files)
David Jamesc0f158a2011-02-22 16:07:29 -0800455 if len(failed_uploads) > 1 or (None not in failed_uploads):
David Jamesf4db1122011-03-17 16:18:05 -0700456 error_msg = ['%s -> %s\n' % args for args in failed_uploads if args]
David Jamesc0f158a2011-02-22 16:07:29 -0800457 raise UploadFailed('Error uploading:\n%s' % error_msg)
458 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400459 pkgs = [p['CPV'] + '.tbz2' for p in uploads]
David Jamesc0f158a2011-02-22 16:07:29 -0800460 ssh_server, remote_path = remote_location.split(':', 1)
Peter Mayo193f68f2011-04-19 19:08:21 -0400461 remote_path = remote_path.rstrip('/')
462 pkg_index = tmp_packages_file.name
463 remote_location = remote_location.rstrip('/')
464 remote_packages = '%s/Packages' % remote_location
465 cmds = [['ssh', ssh_server, 'mkdir', '-p', remote_path],
466 ['rsync', '-av', '--chmod=a+r', pkg_index, remote_packages]]
David Jamesc0f158a2011-02-22 16:07:29 -0800467 if pkgs:
Peter Mayo193f68f2011-04-19 19:08:21 -0400468 cmds.append(['rsync', '-Rav'] + pkgs + [remote_location + '/'])
David Jamesc0f158a2011-02-22 16:07:29 -0800469 for cmd in cmds:
Peter Mayo193f68f2011-04-19 19:08:21 -0400470 if not _RetryRun(cmd, cwd=package_path):
471 raise UploadFailed('Could not run %r' % cmd)
David James8c846492011-01-25 17:07:29 -0800472
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200473 def _UploadBoardTarball(self, board_path, url_suffix, version):
David James8fa34ea2011-04-15 13:00:20 -0700474 """Upload a tarball of the board at the specified path to Google Storage.
475
476 Args:
477 board_path: The path to the board dir.
478 url_suffix: The remote subdirectory where we should upload the packages.
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200479 version: The version of the board.
David James8fa34ea2011-04-15 13:00:20 -0700480 """
481 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
482 assert remote_location.startswith('gs://')
483 cwd, boardname = os.path.split(board_path.rstrip(os.path.sep))
484 tmpdir = tempfile.mkdtemp()
485 try:
486 tarfile = os.path.join(tmpdir, '%s.tbz2' % boardname)
487 cmd = ['sudo', 'tar', '-I', 'pbzip2', '-cf', tarfile]
488 excluded_paths = ('usr/lib/debug', 'usr/local/autotest', 'packages',
489 'tmp')
490 for path in excluded_paths:
Zdenek Behane3ed3462011-06-16 00:36:08 +0200491 cmd.append('--exclude=%s/*' % path)
492 cmd.append('.')
493 cros_build_lib.RunCommand(cmd, cwd=os.path.join(cwd, boardname))
David James8fa34ea2011-04-15 13:00:20 -0700494 remote_tarfile = '%s/%s.tbz2' % (remote_location.rstrip('/'), boardname)
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200495 # FIXME(zbehan): Temporary hack to upload amd64-host chroots to a
496 # different gs bucket. The right way is to do the upload in a separate
497 # pass of this script.
498 if boardname == 'amd64-host':
Zdenek Behanaf3c9002011-06-24 10:07:40 +0200499 # FIXME(zbehan): Why does version contain the prefix "chroot-"?
500 remote_tarfile = \
501 'gs://chromiumos-sdk/cros-sdk-%s.tbz2' % version.strip('chroot-')
David James8fa34ea2011-04-15 13:00:20 -0700502 if _GsUpload((tarfile, remote_tarfile, self._acl)):
503 sys.exit(1)
504 finally:
505 cros_build_lib.RunCommand(['sudo', 'rm', '-rf', tmpdir], cwd=cwd)
506
David James615e5b52011-06-03 11:10:15 -0700507 def _SyncHostPrebuilts(self, version, key, git_sync, sync_binhost_conf):
David Jamesc0f158a2011-02-22 16:07:29 -0800508 """Synchronize host prebuilt files.
David James05bcb2b2011-02-09 09:25:47 -0800509
David Jamesc0f158a2011-02-22 16:07:29 -0800510 This function will sync both the standard host packages, plus the host
511 packages associated with all targets that have been "setup" with the
512 current host's chroot. For instance, if this host has been used to build
513 x86-generic, it will sync the host packages associated with
514 'i686-pc-linux-gnu'. If this host has also been used to build arm-generic,
515 it will also sync the host packages associated with
516 'armv7a-cros-linux-gnueabi'.
David James05bcb2b2011-02-09 09:25:47 -0800517
David Jamesc0f158a2011-02-22 16:07:29 -0800518 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800519 version: A unique string, intended to be included in the upload path,
520 which identifies the version number of the uploaded prebuilts.
521 key: The variable key to update in the git file.
522 git_sync: If set, update make.conf of target to reference the latest
523 prebuilt packages generated here.
524 sync_binhost_conf: If set, update binhost config file in
525 chromiumos-overlay for the host.
526 """
David Jamese2488642011-11-14 16:15:20 -0800527 # Slave boards are listed before the master board so that the master board
528 # takes priority (i.e. x86-generic preflight host prebuilts takes priority
529 # over preflight host prebuilts from other builders.)
530 binhost_urls = []
David James4058b0d2011-12-08 21:24:50 -0800531 for target in self._slave_targets + [self._target]:
532 url_suffix = _REL_HOST_PATH % {'version': version,
533 'host_arch': _HOST_ARCH,
534 'target': target}
David Jamese2488642011-11-14 16:15:20 -0800535 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James05bcb2b2011-02-09 09:25:47 -0800536
David James4058b0d2011-12-08 21:24:50 -0800537 if self._target == target and not self._skip_upload and not self._debug:
David Jamese2488642011-11-14 16:15:20 -0800538 # Upload prebuilts.
539 package_path = os.path.join(self._build_path, _HOST_PACKAGES_PATH)
540 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8ece7ee2011-06-29 16:02:30 -0700541
David Jamese2488642011-11-14 16:15:20 -0800542 # Record URL where prebuilts were uploaded.
543 binhost_urls.append('%s/%s/' % (self._binhost_base_url.rstrip('/'),
544 packages_url_suffix.rstrip('/')))
545
David James20b2b6f2011-11-18 15:11:58 -0800546 binhost = ' '.join(binhost_urls)
David James8ece7ee2011-06-29 16:02:30 -0700547 if git_sync:
548 git_file = os.path.join(self._build_path,
David James4058b0d2011-12-08 21:24:50 -0800549 _PREBUILT_MAKE_CONF[_HOST_ARCH])
David Jamese2488642011-11-14 16:15:20 -0800550 RevGitFile(git_file, binhost, key=key, dryrun=self._debug)
David James8ece7ee2011-06-29 16:02:30 -0700551 if sync_binhost_conf:
David James32b0b2f2011-07-13 20:56:50 -0700552 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James4058b0d2011-12-08 21:24:50 -0800553 'host', '%s-%s.conf' % (_HOST_ARCH, key))
David Jamese2488642011-11-14 16:15:20 -0800554 UpdateBinhostConfFile(binhost_conf, key, binhost)
David Jamesc0f158a2011-02-22 16:07:29 -0800555
David Jamese2488642011-11-14 16:15:20 -0800556 def _SyncBoardPrebuilts(self, version, key, git_sync, sync_binhost_conf,
557 upload_board_tarball):
David Jamesc0f158a2011-02-22 16:07:29 -0800558 """Synchronize board prebuilt files.
559
560 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800561 version: A unique string, intended to be included in the upload path,
562 which identifies the version number of the uploaded prebuilts.
563 key: The variable key to update in the git file.
564 git_sync: If set, update make.conf of target to reference the latest
565 prebuilt packages generated here.
566 sync_binhost_conf: If set, update binhost config file in
567 chromiumos-overlay for the current board.
David James8fa34ea2011-04-15 13:00:20 -0700568 upload_board_tarball: Include a tarball of the board in our upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800569 """
David James4058b0d2011-12-08 21:24:50 -0800570 for target in self._slave_targets + [self._target]:
David Jamese2488642011-11-14 16:15:20 -0800571 board_path = os.path.join(self._build_path,
David James4058b0d2011-12-08 21:24:50 -0800572 _BOARD_PATH % {'board': target.board_variant})
David Jamese2488642011-11-14 16:15:20 -0800573 package_path = os.path.join(board_path, 'packages')
David James4058b0d2011-12-08 21:24:50 -0800574 url_suffix = _REL_BOARD_PATH % {'target': target, 'version': version}
David Jamese2488642011-11-14 16:15:20 -0800575 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James8fa34ea2011-04-15 13:00:20 -0700576
David James4058b0d2011-12-08 21:24:50 -0800577 if self._target == target and not self._skip_upload and not self._debug:
David Jamese2488642011-11-14 16:15:20 -0800578 # Upload board tarballs in the background.
579 if upload_board_tarball:
580 tar_process = multiprocessing.Process(target=self._UploadBoardTarball,
581 args=(board_path, url_suffix,
582 version))
583 tar_process.start()
David James8fa34ea2011-04-15 13:00:20 -0700584
David Jamese2488642011-11-14 16:15:20 -0800585 # Upload prebuilts.
586 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8fa34ea2011-04-15 13:00:20 -0700587
David Jamese2488642011-11-14 16:15:20 -0800588 # Make sure we finished uploading the board tarballs.
589 if upload_board_tarball:
590 tar_process.join()
591 assert tar_process.exitcode == 0
592 # TODO(zbehan): This should be done cleaner.
David James4058b0d2011-12-08 21:24:50 -0800593 if target.board == 'amd64-host':
David Jamese2488642011-11-14 16:15:20 -0800594 sdk_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James8ece7ee2011-06-29 16:02:30 -0700595 'host/sdk_version.conf')
David Jamese2488642011-11-14 16:15:20 -0800596 RevGitFile(sdk_conf, version.strip('chroot-'),
597 key='SDK_LATEST_VERSION', dryrun=self._debug)
David Jamesc0f158a2011-02-22 16:07:29 -0800598
David Jamese2488642011-11-14 16:15:20 -0800599 # Record URL where prebuilts were uploaded.
600 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
601 packages_url_suffix.rstrip('/'))
602
603 if git_sync:
David James4058b0d2011-12-08 21:24:50 -0800604 git_file = DeterminePrebuiltConfFile(self._build_path, target)
David Jamese2488642011-11-14 16:15:20 -0800605 RevGitFile(git_file, url_value, key=key, dryrun=self._debug)
606 if sync_binhost_conf:
607 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James4058b0d2011-12-08 21:24:50 -0800608 'target', '%s-%s.conf' % (target, key))
David Jamese2488642011-11-14 16:15:20 -0800609 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800610
611
David James8c846492011-01-25 17:07:29 -0800612def usage(parser, msg):
613 """Display usage message and parser help then exit with 1."""
614 print >> sys.stderr, msg
615 parser.print_help()
616 sys.exit(1)
617
David James4058b0d2011-12-08 21:24:50 -0800618
619def add_slave_board(_option, _opt_str, value, parser):
620 parser.values.slave_targets.append(BuildTarget(value))
621
622
623def add_slave_profile(_option, _opt_str, value, parser):
624 if not parser.values.slave_targets:
625 usage(parser, 'Must specify --slave-board before --slave-profile')
626 if parser.values.slave_targets[-1].profile is not None:
627 usage(parser, 'Cannot specify --slave-profile twice for same board')
628 parser.values.slave_targets[-1].profile = value
629
630
David Jamesc0f158a2011-02-22 16:07:29 -0800631def ParseOptions():
David James8c846492011-01-25 17:07:29 -0800632 parser = optparse.OptionParser()
633 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
634 default=_BINHOST_BASE_URL,
635 help='Base URL to use for binhost in make.conf updates')
636 parser.add_option('', '--previous-binhost-url', action='append',
637 default=[], dest='previous_binhost_url',
638 help='Previous binhost URL')
639 parser.add_option('-b', '--board', dest='board', default=None,
640 help='Board type that was built on this machine')
David James4058b0d2011-12-08 21:24:50 -0800641 parser.add_option('', '--profile', dest='profile', default=None,
642 help='Profile that was built on this machine')
643 parser.add_option('', '--slave-board', default=[], action='callback',
644 dest='slave_targets', type='string',
645 callback=add_slave_board,
646 help='Board type that was built on a slave machine. To '
647 'add a profile to this board, use --slave-profile.')
648 parser.add_option('', '--slave-profile', action='callback', type='string',
649 callback=add_slave_profile,
650 help='Board profile that was built on a slave machine. '
651 'Applies to previous slave board.')
David James8c846492011-01-25 17:07:29 -0800652 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800653 help='Path to the directory containing the chroot')
David James615e5b52011-06-03 11:10:15 -0700654 parser.add_option('', '--packages', action='append',
655 default=[], dest='packages',
656 help='Only include the specified packages. '
657 '(Default is to include all packages.)')
David James8c846492011-01-25 17:07:29 -0800658 parser.add_option('-s', '--sync-host', dest='sync_host',
659 default=False, action='store_true',
660 help='Sync host prebuilts')
661 parser.add_option('-g', '--git-sync', dest='git_sync',
662 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800663 help='Enable git version sync (This commits to a repo.) '
664 'This is used by full builders to commit directly '
665 'to board overlays.')
David James8c846492011-01-25 17:07:29 -0800666 parser.add_option('-u', '--upload', dest='upload',
667 default=None,
668 help='Upload location')
669 parser.add_option('-V', '--prepend-version', dest='prepend_version',
670 default=None,
671 help='Add an identifier to the front of the version')
672 parser.add_option('-f', '--filters', dest='filters', action='store_true',
673 default=False,
674 help='Turn on filtering of private ebuild packages')
675 parser.add_option('-k', '--key', dest='key',
676 default='PORTAGE_BINHOST',
677 help='Key to update in make.conf / binhost.conf')
David James8ece7ee2011-06-29 16:02:30 -0700678 parser.add_option('', '--set-version', dest='set_version',
679 default=None,
680 help='Specify the version string')
David James8c846492011-01-25 17:07:29 -0800681 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
682 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800683 help='Update binhost.conf in chromiumos-overlay or '
684 'chromeos-overlay. Commit the changes, but don\'t '
685 'push them. This is used for preflight binhosts.')
David James32b0b2f2011-07-13 20:56:50 -0700686 parser.add_option('', '--binhost-conf-dir', dest='binhost_conf_dir',
687 default=_BINHOST_CONF_DIR,
688 help='Directory to commit binhost config with '
689 '--sync-binhost-conf.')
David Jamesfd0b0852011-02-23 11:15:36 -0800690 parser.add_option('-P', '--private', dest='private', action='store_true',
691 default=False, help='Mark gs:// uploads as private.')
David James8ece7ee2011-06-29 16:02:30 -0700692 parser.add_option('', '--skip-upload', dest='skip_upload',
693 action='store_true', default=False,
694 help='Skip upload step.')
David James8fa34ea2011-04-15 13:00:20 -0700695 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
696 action='store_true', default=False,
697 help='Upload board tarball to Google Storage.')
David James27fa7d12011-06-29 17:24:14 -0700698 parser.add_option('', '--debug', dest='debug',
699 action='store_true', default=False,
700 help='Don\'t push or upload prebuilts.')
David James8c846492011-01-25 17:07:29 -0800701
702 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800703 if not options.build_path:
704 usage(parser, 'Error: you need provide a chroot path')
David James8ece7ee2011-06-29 16:02:30 -0700705 if not options.upload and not options.skip_upload:
David James8c846492011-01-25 17:07:29 -0800706 usage(parser, 'Error: you need to provide an upload location using -u')
David James8ece7ee2011-06-29 16:02:30 -0700707 if not options.set_version and options.skip_upload:
708 usage(parser, 'Error: If you are using --skip-upload, you must specify a '
709 'version number using --set-version.')
David James9417f272011-05-26 13:24:47 -0700710 if args:
711 usage(parser, 'Error: invalid arguments passed to prebuilt.py: %r' % args)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700712
David James4058b0d2011-12-08 21:24:50 -0800713 options.target = BuildTarget(options.board, options.profile)
714 if options.target in options.slave_targets:
715 usage(parser, 'Error: --board/--profile must not also be a slave target.')
David Jamese2488642011-11-14 16:15:20 -0800716
David James4058b0d2011-12-08 21:24:50 -0800717 if len(set(options.slave_targets)) != len(options.slave_targets):
David Jamese2488642011-11-14 16:15:20 -0800718 usage(parser, 'Error: --slave-boards must not have duplicates.')
719
David James4058b0d2011-12-08 21:24:50 -0800720 if options.slave_targets and options.git_sync:
David Jamese2488642011-11-14 16:15:20 -0800721 usage(parser, 'Error: --slave-boards is not compatible with --git-sync')
722
David James8ece7ee2011-06-29 16:02:30 -0700723 if (options.upload_board_tarball and options.skip_upload and
724 options.board == 'amd64-host'):
725 usage(parser, 'Error: --skip-upload is not compatible with '
726 '--upload-board-tarball and --board=amd64-host')
David James8fa34ea2011-04-15 13:00:20 -0700727
David James8ece7ee2011-06-29 16:02:30 -0700728 if (options.upload_board_tarball and not options.skip_upload and
729 not options.upload.startswith('gs://')):
David James8fa34ea2011-04-15 13:00:20 -0700730 usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
731 '--upload must be a gs:// URL.')
732
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700733 if options.private:
734 if options.sync_host:
735 usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
736 'together, we do not support private host prebuilts')
737
David James8ece7ee2011-06-29 16:02:30 -0700738 if not options.upload or not options.upload.startswith('gs://'):
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700739 usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
740 '--upload must be a gs:// URL.')
741
742 if options.binhost_base_url != _BINHOST_BASE_URL:
David Jamese2488642011-11-14 16:15:20 -0800743 usage(parser, 'Error: when using --private the --binhost-base-url '
744 'is automatically derived.')
David James27fa7d12011-06-29 17:24:14 -0700745
David Jamesc0f158a2011-02-22 16:07:29 -0800746 return options
747
748def main():
David Jamesdb401072011-06-10 12:17:16 -0700749 # Set umask to a sane value so that files created as root are readable.
750 os.umask(022)
751
David Jamesc0f158a2011-02-22 16:07:29 -0800752 options = ParseOptions()
753
David James05bcb2b2011-02-09 09:25:47 -0800754 # Calculate a list of Packages index files to compare against. Whenever we
755 # upload a package, we check to make sure it's not already stored in one of
756 # the packages files we uploaded. This list of packages files might contain
757 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800758 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800759
David James8ece7ee2011-06-29 16:02:30 -0700760 if options.set_version:
761 version = options.set_version
762 else:
763 version = GetVersion()
David Jamesc0f158a2011-02-22 16:07:29 -0800764 if options.prepend_version:
765 version = '%s-%s' % (options.prepend_version, version)
766
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700767 acl = 'public-read'
768 binhost_base_url = options.binhost_base_url
769
770 if options.private:
771 binhost_base_url = options.upload
772 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
David James4058b0d2011-12-08 21:24:50 -0800773 options.target)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700774 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
775
776 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David James615e5b52011-06-03 11:10:15 -0700777 pkg_indexes, options.build_path,
David James27fa7d12011-06-29 17:24:14 -0700778 options.packages, options.skip_upload,
David Jamese2488642011-11-14 16:15:20 -0800779 options.binhost_conf_dir, options.debug,
David James4058b0d2011-12-08 21:24:50 -0800780 options.target, options.slave_targets)
David Jamesc0f158a2011-02-22 16:07:29 -0800781
David James8c846492011-01-25 17:07:29 -0800782 if options.sync_host:
David James615e5b52011-06-03 11:10:15 -0700783 uploader._SyncHostPrebuilts(version, options.key, options.git_sync,
784 options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800785
786 if options.board:
David Jamese2488642011-11-14 16:15:20 -0800787 uploader._SyncBoardPrebuilts(version, options.key, options.git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700788 options.sync_binhost_conf,
789 options.upload_board_tarball)
David James8c846492011-01-25 17:07:29 -0800790
791if __name__ == '__main__':
792 main()