blob: 0ef7ba27d5a252e766ef72c8ca93f0bb5c6a0fcf [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()
Peter Mayo193f68f2011-04-19 19:08:21 -0400155 git_ssh_config_cmd = [
156 'git',
157 'config',
Chris Sosac13bba52011-05-24 15:14:09 -0700158 'url.ssh://gerrit.chromium.org:29418.pushinsteadof',
David James1b6e67a2011-05-19 21:32:38 -0700159 'http://git.chromium.org']
160 cros_build_lib.RunCommand(git_ssh_config_cmd, cwd=cwd)
David Jamesbcfdc5d2011-08-25 20:46:33 -0700161 _RetryRun(['git', 'remote', 'update'], cwd=cwd)
David James1b6e67a2011-05-19 21:32:38 -0700162 cros_build_lib.RunCommand(['repo', 'start', prebuilt_branch, '.'], cwd=cwd)
David James6181b892011-06-08 16:45:07 -0700163
164 # We want to push our changes to this file to tip of tree, so we should
165 # make sure the branch is at tip of tree before we apply our change.
166 push_branch = '%s/%s' % cros_build_lib.GetPushBranch(prebuilt_branch, cwd)
167 cros_build_lib.RunCommand(['git', 'reset', '--hard', push_branch], cwd=cwd)
168
David James8c846492011-01-25 17:07:29 -0800169 description = 'Update %s="%s" in %s' % (key, value, filename)
170 print description
171 try:
172 UpdateLocalFile(filename, value, key)
David James1b6e67a2011-05-19 21:32:38 -0700173 cros_build_lib.RunCommand(['git', 'config', 'push.default', 'tracking'],
174 cwd=cwd)
175 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
176 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James27fa7d12011-06-29 17:24:14 -0700177 cros_build_lib.GitPushWithRetry(prebuilt_branch, cwd=cwd, dryrun=dryrun)
David James8c846492011-01-25 17:07:29 -0800178 finally:
David James1b6e67a2011-05-19 21:32:38 -0700179 cros_build_lib.RunCommand(['git', 'checkout', commit], cwd=cwd)
180 cros_build_lib.RunCommand(['repo', 'abandon', 'prebuilt_branch', '.'],
181 cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800182
183
184def GetVersion():
185 """Get the version to put in LATEST and update the git version with."""
186 return datetime.datetime.now().strftime('%d.%m.%y.%H%M%S')
187
188
Peter Mayo193f68f2011-04-19 19:08:21 -0400189def _RetryRun(cmd, print_cmd=True, cwd=None):
David James8c846492011-01-25 17:07:29 -0800190 """Run the specified command, retrying if necessary.
191
192 Args:
193 cmd: The command to run.
194 print_cmd: Whether to print out the cmd.
195 shell: Whether to treat the command as a shell.
196 cwd: Working directory to run command in.
197
198 Returns:
199 True if the command succeeded. Otherwise, returns False.
200 """
201
202 # TODO(scottz): port to use _Run or similar when it is available in
203 # cros_build_lib.
Chris Sosa58669192011-06-30 12:45:03 -0700204 for unused_attempt in range(_RETRIES):
David James8c846492011-01-25 17:07:29 -0800205 try:
Chris Sosa58669192011-06-30 12:45:03 -0700206 cros_build_lib.RunCommand(cmd, print_cmd=print_cmd, cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800207 return True
208 except cros_build_lib.RunCommandError:
Peter Mayo193f68f2011-04-19 19:08:21 -0400209 print 'Failed to run %r' % cmd
David James8c846492011-01-25 17:07:29 -0800210 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400211 print 'Retry failed run %r, giving up' % cmd
David James8c846492011-01-25 17:07:29 -0800212 return False
213
214
215def _GsUpload(args):
216 """Upload to GS bucket.
217
218 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800219 args: a tuple of three arguments that contains local_file, remote_file, and
220 the acl used for uploading the file.
David James8c846492011-01-25 17:07:29 -0800221
222 Returns:
223 Return the arg tuple of two if the upload failed
224 """
David Jamesfd0b0852011-02-23 11:15:36 -0800225 (local_file, remote_file, acl) = args
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700226 CANNED_ACLS = ['public-read', 'private', 'bucket-owner-read',
227 'authenticated-read', 'bucket-owner-full-control',
228 'public-read-write']
229 acl_cmd = None
230 if acl in CANNED_ACLS:
Peter Mayo193f68f2011-04-19 19:08:21 -0400231 cmd = [_GSUTIL_BIN, 'cp', '-a', acl, local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700232 else:
233 # For private uploads we assume that the overlay board is set up properly
234 # and a googlestore_acl.xml is present, if not this script errors
Peter Mayo193f68f2011-04-19 19:08:21 -0400235 cmd = [_GSUTIL_BIN, 'cp', '-a', 'private', local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700236 if not os.path.exists(acl):
237 print >> sys.stderr, ('You are specifying either a file that does not '
238 'exist or an unknown canned acl: %s. Aborting '
239 'upload') % acl
240 # emulate the failing of an upload since we are not uploading the file
241 return (local_file, remote_file)
David James8c846492011-01-25 17:07:29 -0800242
Peter Mayo193f68f2011-04-19 19:08:21 -0400243 acl_cmd = [_GSUTIL_BIN, 'setacl', acl, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700244
Peter Mayo193f68f2011-04-19 19:08:21 -0400245 if not _RetryRun(cmd, print_cmd=False):
David James8c846492011-01-25 17:07:29 -0800246 return (local_file, remote_file)
247
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700248 if acl_cmd:
249 # Apply the passed in ACL xml file to the uploaded object.
Peter Mayo193f68f2011-04-19 19:08:21 -0400250 _RetryRun(acl_cmd, print_cmd=False)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700251
252
David Jamesfd0b0852011-02-23 11:15:36 -0800253def RemoteUpload(acl, files, pool=10):
David James8c846492011-01-25 17:07:29 -0800254 """Upload to google storage.
255
256 Create a pool of process and call _GsUpload with the proper arguments.
257
258 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800259 acl: The canned acl used for uploading. acl can be one of: "public-read",
260 "public-read-write", "authenticated-read", "bucket-owner-read",
261 "bucket-owner-full-control", or "private".
David James8c846492011-01-25 17:07:29 -0800262 files: dictionary with keys to local files and values to remote path.
263 pool: integer of maximum proesses to have at the same time.
264
265 Returns:
266 Return a set of tuple arguments of the failed uploads
267 """
268 # TODO(scottz) port this to use _RunManyParallel when it is available in
269 # cros_build_lib
270 pool = multiprocessing.Pool(processes=pool)
271 workers = []
272 for local_file, remote_path in files.iteritems():
David Jamesfd0b0852011-02-23 11:15:36 -0800273 workers.append((local_file, remote_path, acl))
David James8c846492011-01-25 17:07:29 -0800274
275 result = pool.map_async(_GsUpload, workers, chunksize=1)
276 while True:
277 try:
Chris Sosa471532a2011-02-01 15:10:06 -0800278 return set(result.get(60 * 60))
David James8c846492011-01-25 17:07:29 -0800279 except multiprocessing.TimeoutError:
280 pass
281
282
283def GenerateUploadDict(base_local_path, base_remote_path, pkgs):
284 """Build a dictionary of local remote file key pairs to upload.
285
286 Args:
287 base_local_path: The base path to the files on the local hard drive.
288 remote_path: The base path to the remote paths.
289 pkgs: The packages to upload.
290
291 Returns:
292 Returns a dictionary of local_path/remote_path pairs
293 """
294 upload_files = {}
295 for pkg in pkgs:
296 suffix = pkg['CPV'] + '.tbz2'
297 local_path = os.path.join(base_local_path, suffix)
298 assert os.path.exists(local_path)
299 remote_path = '%s/%s' % (base_remote_path.rstrip('/'), suffix)
300 upload_files[local_path] = remote_path
301
302 return upload_files
303
304def GetBoardPathFromCrosOverlayList(build_path, target):
305 """Use the cros_overlay_list to determine the path to the board overlay
306 Args:
307 build_path: The path to the root of the build directory
308 target: The target that we are looking for, could consist of board and
309 board_variant, we handle that properly
310 Returns:
311 The last line from cros_overlay_list as a string
312 """
Chris Sosa471532a2011-02-01 15:10:06 -0800313 script_dir = os.path.join(build_path, 'src/platform/dev/host')
David James4058b0d2011-12-08 21:24:50 -0800314 cmd = ['./cros_overlay_list', '--board', target.board]
315 if target.variant:
316 cmd += ['--variant', target.variant]
David James8c846492011-01-25 17:07:29 -0800317
318 cmd_output = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
319 cwd=script_dir)
320 # We only care about the last entry
321 return cmd_output.output.splitlines().pop()
322
323
324def DeterminePrebuiltConfFile(build_path, target):
325 """Determine the prebuilt.conf file that needs to be updated for prebuilts.
326
327 Args:
328 build_path: The path to the root of the build directory
329 target: String representation of the board. This includes host and board
330 targets
331
332 Returns
333 A string path to a prebuilt.conf file to be updated.
334 """
David James4058b0d2011-12-08 21:24:50 -0800335 if _HOST_ARCH == target:
David James8c846492011-01-25 17:07:29 -0800336 # We are host.
337 # Without more examples of hosts this is a kludge for now.
338 # TODO(Scottz): as new host targets come online expand this to
339 # work more like boards.
Chris Sosa471532a2011-02-01 15:10:06 -0800340 make_path = _PREBUILT_MAKE_CONF[target]
David James8c846492011-01-25 17:07:29 -0800341 else:
342 # We are a board
343 board = GetBoardPathFromCrosOverlayList(build_path, target)
344 make_path = os.path.join(board, 'prebuilt.conf')
345
346 return make_path
347
348
349def UpdateBinhostConfFile(path, key, value):
350 """Update binhost config file file with key=value.
351
352 Args:
353 path: Filename to update.
354 key: Key to update.
355 value: New value for key.
356 """
357 cwd = os.path.dirname(os.path.abspath(path))
358 filename = os.path.basename(path)
359 if not os.path.isdir(cwd):
360 os.makedirs(cwd)
361 if not os.path.isfile(path):
362 config_file = file(path, 'w')
David James8c846492011-01-25 17:07:29 -0800363 config_file.close()
364 UpdateLocalFile(path, value, key)
Chris Sosac13bba52011-05-24 15:14:09 -0700365 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
David James20b2b6f2011-11-18 15:11:58 -0800366 description = 'Update %s="%s" in %s' % (key, value, filename)
Peter Mayo193f68f2011-04-19 19:08:21 -0400367 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800368
369
David Jamesce093af2011-02-23 15:21:58 -0800370def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800371 """Grab all of the packages files associated with a list of binhost_urls.
372
David James05bcb2b2011-02-09 09:25:47 -0800373 Args:
374 binhost_urls: The URLs for the directories containing the Packages files we
375 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800376
377 Returns:
378 A list of PackageIndex objects.
379 """
380 pkg_indexes = []
381 for url in binhost_urls:
382 pkg_index = GrabRemotePackageIndex(url)
383 if pkg_index:
384 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800385 return pkg_indexes
386
387
David James05bcb2b2011-02-09 09:25:47 -0800388
David Jamesc0f158a2011-02-22 16:07:29 -0800389class PrebuiltUploader(object):
390 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800391
David James615e5b52011-06-03 11:10:15 -0700392 def __init__(self, upload_location, acl, binhost_base_url,
David James32b0b2f2011-07-13 20:56:50 -0700393 pkg_indexes, build_path, packages, skip_upload,
David James4058b0d2011-12-08 21:24:50 -0800394 binhost_conf_dir, debug, target, slave_targets):
David Jamesc0f158a2011-02-22 16:07:29 -0800395 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800396
David Jamesc0f158a2011-02-22 16:07:29 -0800397 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800398
David Jamesc0f158a2011-02-22 16:07:29 -0800399 Args:
400 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800401 acl: The canned acl used for uploading to Google Storage. acl can be one
402 of: "public-read", "public-read-write", "authenticated-read",
403 "bucket-owner-read", "bucket-owner-full-control", or "private". If
404 we are not uploading to Google Storage, this parameter is unused.
405 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800406 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
407 uploading duplicate files, we just link to the old files.
David James615e5b52011-06-03 11:10:15 -0700408 build_path: The path to the directory containing the chroot.
409 packages: Packages to upload.
David James32b0b2f2011-07-13 20:56:50 -0700410 skip_upload: Don't actually upload the tarballs.
411 binhost_conf_dir: Directory where to store binhost.conf files.
412 debug: Don't push or upload prebuilts.
David James4058b0d2011-12-08 21:24:50 -0800413 target: BuildTarget managed by this builder.
414 slave_targets: List of BuildTargets managed by slave builders.
David Jamesc0f158a2011-02-22 16:07:29 -0800415 """
416 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800417 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800418 self._binhost_base_url = binhost_base_url
419 self._pkg_indexes = pkg_indexes
David James615e5b52011-06-03 11:10:15 -0700420 self._build_path = build_path
421 self._packages = set(packages)
David James8ece7ee2011-06-29 16:02:30 -0700422 self._skip_upload = skip_upload
David James32b0b2f2011-07-13 20:56:50 -0700423 self._binhost_conf_dir = binhost_conf_dir
David James27fa7d12011-06-29 17:24:14 -0700424 self._debug = debug
David James4058b0d2011-12-08 21:24:50 -0800425 self._target = target
426 self._slave_targets = slave_targets
David James615e5b52011-06-03 11:10:15 -0700427
428 def _ShouldFilterPackage(self, pkg):
429 if not self._packages:
430 return False
431 pym_path = os.path.abspath(os.path.join(self._build_path, _PYM_PATH))
432 sys.path.append(pym_path)
433 import portage.versions
434 cat, pkgname = portage.versions.catpkgsplit(pkg['CPV'])[0:2]
435 cp = '%s/%s' % (cat, pkgname)
436 return pkgname not in self._packages and cp not in self._packages
David James8c846492011-01-25 17:07:29 -0800437
David Jamesc0f158a2011-02-22 16:07:29 -0800438 def _UploadPrebuilt(self, package_path, url_suffix):
439 """Upload host or board prebuilt files to Google Storage space.
David James8c846492011-01-25 17:07:29 -0800440
David Jamesc0f158a2011-02-22 16:07:29 -0800441 Args:
442 package_path: The path to the packages dir.
David Jamesce093af2011-02-23 15:21:58 -0800443 url_suffix: The remote subdirectory where we should upload the packages.
David Jamesa3bba142011-05-26 21:24:20 -0700444
David Jamesc0f158a2011-02-22 16:07:29 -0800445 """
David James8c846492011-01-25 17:07:29 -0800446
David Jamesc0f158a2011-02-22 16:07:29 -0800447 # Process Packages file, removing duplicates and filtered packages.
448 pkg_index = GrabLocalPackageIndex(package_path)
449 pkg_index.SetUploadLocation(self._binhost_base_url, url_suffix)
David James615e5b52011-06-03 11:10:15 -0700450 pkg_index.RemoveFilteredPackages(self._ShouldFilterPackage)
David Jamesc0f158a2011-02-22 16:07:29 -0800451 uploads = pkg_index.ResolveDuplicateUploads(self._pkg_indexes)
David James05bcb2b2011-02-09 09:25:47 -0800452
David Jamesc0f158a2011-02-22 16:07:29 -0800453 # Write Packages file.
454 tmp_packages_file = pkg_index.WriteToNamedTemporaryFile()
David James05bcb2b2011-02-09 09:25:47 -0800455
David Jamesc0f158a2011-02-22 16:07:29 -0800456 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
457 if remote_location.startswith('gs://'):
458 # Build list of files to upload.
459 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
460 remote_file = '%s/Packages' % remote_location.rstrip('/')
461 upload_files[tmp_packages_file.name] = remote_file
David James05bcb2b2011-02-09 09:25:47 -0800462
David Jamesfd0b0852011-02-23 11:15:36 -0800463 failed_uploads = RemoteUpload(self._acl, upload_files)
David Jamesc0f158a2011-02-22 16:07:29 -0800464 if len(failed_uploads) > 1 or (None not in failed_uploads):
David Jamesf4db1122011-03-17 16:18:05 -0700465 error_msg = ['%s -> %s\n' % args for args in failed_uploads if args]
David Jamesc0f158a2011-02-22 16:07:29 -0800466 raise UploadFailed('Error uploading:\n%s' % error_msg)
467 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400468 pkgs = [p['CPV'] + '.tbz2' for p in uploads]
David Jamesc0f158a2011-02-22 16:07:29 -0800469 ssh_server, remote_path = remote_location.split(':', 1)
Peter Mayo193f68f2011-04-19 19:08:21 -0400470 remote_path = remote_path.rstrip('/')
471 pkg_index = tmp_packages_file.name
472 remote_location = remote_location.rstrip('/')
473 remote_packages = '%s/Packages' % remote_location
474 cmds = [['ssh', ssh_server, 'mkdir', '-p', remote_path],
475 ['rsync', '-av', '--chmod=a+r', pkg_index, remote_packages]]
David Jamesc0f158a2011-02-22 16:07:29 -0800476 if pkgs:
Peter Mayo193f68f2011-04-19 19:08:21 -0400477 cmds.append(['rsync', '-Rav'] + pkgs + [remote_location + '/'])
David Jamesc0f158a2011-02-22 16:07:29 -0800478 for cmd in cmds:
Peter Mayo193f68f2011-04-19 19:08:21 -0400479 if not _RetryRun(cmd, cwd=package_path):
480 raise UploadFailed('Could not run %r' % cmd)
David James8c846492011-01-25 17:07:29 -0800481
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200482 def _UploadBoardTarball(self, board_path, url_suffix, version):
David James8fa34ea2011-04-15 13:00:20 -0700483 """Upload a tarball of the board at the specified path to Google Storage.
484
485 Args:
486 board_path: The path to the board dir.
487 url_suffix: The remote subdirectory where we should upload the packages.
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200488 version: The version of the board.
David James8fa34ea2011-04-15 13:00:20 -0700489 """
490 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
491 assert remote_location.startswith('gs://')
492 cwd, boardname = os.path.split(board_path.rstrip(os.path.sep))
493 tmpdir = tempfile.mkdtemp()
494 try:
495 tarfile = os.path.join(tmpdir, '%s.tbz2' % boardname)
496 cmd = ['sudo', 'tar', '-I', 'pbzip2', '-cf', tarfile]
497 excluded_paths = ('usr/lib/debug', 'usr/local/autotest', 'packages',
498 'tmp')
499 for path in excluded_paths:
Zdenek Behane3ed3462011-06-16 00:36:08 +0200500 cmd.append('--exclude=%s/*' % path)
501 cmd.append('.')
502 cros_build_lib.RunCommand(cmd, cwd=os.path.join(cwd, boardname))
David James8fa34ea2011-04-15 13:00:20 -0700503 remote_tarfile = '%s/%s.tbz2' % (remote_location.rstrip('/'), boardname)
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200504 # FIXME(zbehan): Temporary hack to upload amd64-host chroots to a
505 # different gs bucket. The right way is to do the upload in a separate
506 # pass of this script.
507 if boardname == 'amd64-host':
Zdenek Behanaf3c9002011-06-24 10:07:40 +0200508 # FIXME(zbehan): Why does version contain the prefix "chroot-"?
509 remote_tarfile = \
510 'gs://chromiumos-sdk/cros-sdk-%s.tbz2' % version.strip('chroot-')
David James8fa34ea2011-04-15 13:00:20 -0700511 if _GsUpload((tarfile, remote_tarfile, self._acl)):
512 sys.exit(1)
513 finally:
514 cros_build_lib.RunCommand(['sudo', 'rm', '-rf', tmpdir], cwd=cwd)
515
David James615e5b52011-06-03 11:10:15 -0700516 def _SyncHostPrebuilts(self, version, key, git_sync, sync_binhost_conf):
David Jamesc0f158a2011-02-22 16:07:29 -0800517 """Synchronize host prebuilt files.
David James05bcb2b2011-02-09 09:25:47 -0800518
David Jamesc0f158a2011-02-22 16:07:29 -0800519 This function will sync both the standard host packages, plus the host
520 packages associated with all targets that have been "setup" with the
521 current host's chroot. For instance, if this host has been used to build
522 x86-generic, it will sync the host packages associated with
523 'i686-pc-linux-gnu'. If this host has also been used to build arm-generic,
524 it will also sync the host packages associated with
525 'armv7a-cros-linux-gnueabi'.
David James05bcb2b2011-02-09 09:25:47 -0800526
David Jamesc0f158a2011-02-22 16:07:29 -0800527 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800528 version: A unique string, intended to be included in the upload path,
529 which identifies the version number of the uploaded prebuilts.
530 key: The variable key to update in the git file.
531 git_sync: If set, update make.conf of target to reference the latest
532 prebuilt packages generated here.
533 sync_binhost_conf: If set, update binhost config file in
534 chromiumos-overlay for the host.
535 """
David Jamese2488642011-11-14 16:15:20 -0800536 # Slave boards are listed before the master board so that the master board
537 # takes priority (i.e. x86-generic preflight host prebuilts takes priority
538 # over preflight host prebuilts from other builders.)
539 binhost_urls = []
David James4058b0d2011-12-08 21:24:50 -0800540 for target in self._slave_targets + [self._target]:
541 url_suffix = _REL_HOST_PATH % {'version': version,
542 'host_arch': _HOST_ARCH,
543 'target': target}
David Jamese2488642011-11-14 16:15:20 -0800544 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James05bcb2b2011-02-09 09:25:47 -0800545
David James4058b0d2011-12-08 21:24:50 -0800546 if self._target == target and not self._skip_upload and not self._debug:
David Jamese2488642011-11-14 16:15:20 -0800547 # Upload prebuilts.
548 package_path = os.path.join(self._build_path, _HOST_PACKAGES_PATH)
549 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8ece7ee2011-06-29 16:02:30 -0700550
David Jamese2488642011-11-14 16:15:20 -0800551 # Record URL where prebuilts were uploaded.
552 binhost_urls.append('%s/%s/' % (self._binhost_base_url.rstrip('/'),
553 packages_url_suffix.rstrip('/')))
554
David James20b2b6f2011-11-18 15:11:58 -0800555 binhost = ' '.join(binhost_urls)
David James8ece7ee2011-06-29 16:02:30 -0700556 if git_sync:
557 git_file = os.path.join(self._build_path,
David James4058b0d2011-12-08 21:24:50 -0800558 _PREBUILT_MAKE_CONF[_HOST_ARCH])
David Jamese2488642011-11-14 16:15:20 -0800559 RevGitFile(git_file, binhost, key=key, dryrun=self._debug)
David James8ece7ee2011-06-29 16:02:30 -0700560 if sync_binhost_conf:
David James32b0b2f2011-07-13 20:56:50 -0700561 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James4058b0d2011-12-08 21:24:50 -0800562 'host', '%s-%s.conf' % (_HOST_ARCH, key))
David Jamese2488642011-11-14 16:15:20 -0800563 UpdateBinhostConfFile(binhost_conf, key, binhost)
David Jamesc0f158a2011-02-22 16:07:29 -0800564
David Jamese2488642011-11-14 16:15:20 -0800565 def _SyncBoardPrebuilts(self, version, key, git_sync, sync_binhost_conf,
566 upload_board_tarball):
David Jamesc0f158a2011-02-22 16:07:29 -0800567 """Synchronize board prebuilt files.
568
569 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800570 version: A unique string, intended to be included in the upload path,
571 which identifies the version number of the uploaded prebuilts.
572 key: The variable key to update in the git file.
573 git_sync: If set, update make.conf of target to reference the latest
574 prebuilt packages generated here.
575 sync_binhost_conf: If set, update binhost config file in
576 chromiumos-overlay for the current board.
David James8fa34ea2011-04-15 13:00:20 -0700577 upload_board_tarball: Include a tarball of the board in our upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800578 """
David James4058b0d2011-12-08 21:24:50 -0800579 for target in self._slave_targets + [self._target]:
David Jamese2488642011-11-14 16:15:20 -0800580 board_path = os.path.join(self._build_path,
David James4058b0d2011-12-08 21:24:50 -0800581 _BOARD_PATH % {'board': target.board_variant})
David Jamese2488642011-11-14 16:15:20 -0800582 package_path = os.path.join(board_path, 'packages')
David James4058b0d2011-12-08 21:24:50 -0800583 url_suffix = _REL_BOARD_PATH % {'target': target, 'version': version}
David Jamese2488642011-11-14 16:15:20 -0800584 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James8fa34ea2011-04-15 13:00:20 -0700585
David James4058b0d2011-12-08 21:24:50 -0800586 if self._target == target and not self._skip_upload and not self._debug:
David Jamese2488642011-11-14 16:15:20 -0800587 # Upload board tarballs in the background.
588 if upload_board_tarball:
589 tar_process = multiprocessing.Process(target=self._UploadBoardTarball,
590 args=(board_path, url_suffix,
591 version))
592 tar_process.start()
David James8fa34ea2011-04-15 13:00:20 -0700593
David Jamese2488642011-11-14 16:15:20 -0800594 # Upload prebuilts.
595 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8fa34ea2011-04-15 13:00:20 -0700596
David Jamese2488642011-11-14 16:15:20 -0800597 # Make sure we finished uploading the board tarballs.
598 if upload_board_tarball:
599 tar_process.join()
600 assert tar_process.exitcode == 0
601 # TODO(zbehan): This should be done cleaner.
David James4058b0d2011-12-08 21:24:50 -0800602 if target.board == 'amd64-host':
David Jamese2488642011-11-14 16:15:20 -0800603 sdk_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James8ece7ee2011-06-29 16:02:30 -0700604 'host/sdk_version.conf')
David Jamese2488642011-11-14 16:15:20 -0800605 RevGitFile(sdk_conf, version.strip('chroot-'),
606 key='SDK_LATEST_VERSION', dryrun=self._debug)
David Jamesc0f158a2011-02-22 16:07:29 -0800607
David Jamese2488642011-11-14 16:15:20 -0800608 # Record URL where prebuilts were uploaded.
609 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
610 packages_url_suffix.rstrip('/'))
611
612 if git_sync:
David James4058b0d2011-12-08 21:24:50 -0800613 git_file = DeterminePrebuiltConfFile(self._build_path, target)
David Jamese2488642011-11-14 16:15:20 -0800614 RevGitFile(git_file, url_value, key=key, dryrun=self._debug)
615 if sync_binhost_conf:
616 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James4058b0d2011-12-08 21:24:50 -0800617 'target', '%s-%s.conf' % (target, key))
David Jamese2488642011-11-14 16:15:20 -0800618 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800619
620
David James8c846492011-01-25 17:07:29 -0800621def usage(parser, msg):
622 """Display usage message and parser help then exit with 1."""
623 print >> sys.stderr, msg
624 parser.print_help()
625 sys.exit(1)
626
David James4058b0d2011-12-08 21:24:50 -0800627
628def add_slave_board(_option, _opt_str, value, parser):
629 parser.values.slave_targets.append(BuildTarget(value))
630
631
632def add_slave_profile(_option, _opt_str, value, parser):
633 if not parser.values.slave_targets:
634 usage(parser, 'Must specify --slave-board before --slave-profile')
635 if parser.values.slave_targets[-1].profile is not None:
636 usage(parser, 'Cannot specify --slave-profile twice for same board')
637 parser.values.slave_targets[-1].profile = value
638
639
David Jamesc0f158a2011-02-22 16:07:29 -0800640def ParseOptions():
David James8c846492011-01-25 17:07:29 -0800641 parser = optparse.OptionParser()
642 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
643 default=_BINHOST_BASE_URL,
644 help='Base URL to use for binhost in make.conf updates')
645 parser.add_option('', '--previous-binhost-url', action='append',
646 default=[], dest='previous_binhost_url',
647 help='Previous binhost URL')
648 parser.add_option('-b', '--board', dest='board', default=None,
649 help='Board type that was built on this machine')
David James4058b0d2011-12-08 21:24:50 -0800650 parser.add_option('', '--profile', dest='profile', default=None,
651 help='Profile that was built on this machine')
652 parser.add_option('', '--slave-board', default=[], action='callback',
653 dest='slave_targets', type='string',
654 callback=add_slave_board,
655 help='Board type that was built on a slave machine. To '
656 'add a profile to this board, use --slave-profile.')
657 parser.add_option('', '--slave-profile', action='callback', type='string',
658 callback=add_slave_profile,
659 help='Board profile that was built on a slave machine. '
660 'Applies to previous slave board.')
David James8c846492011-01-25 17:07:29 -0800661 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800662 help='Path to the directory containing the chroot')
David James615e5b52011-06-03 11:10:15 -0700663 parser.add_option('', '--packages', action='append',
664 default=[], dest='packages',
665 help='Only include the specified packages. '
666 '(Default is to include all packages.)')
David James8c846492011-01-25 17:07:29 -0800667 parser.add_option('-s', '--sync-host', dest='sync_host',
668 default=False, action='store_true',
669 help='Sync host prebuilts')
670 parser.add_option('-g', '--git-sync', dest='git_sync',
671 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800672 help='Enable git version sync (This commits to a repo.) '
673 'This is used by full builders to commit directly '
674 'to board overlays.')
David James8c846492011-01-25 17:07:29 -0800675 parser.add_option('-u', '--upload', dest='upload',
676 default=None,
677 help='Upload location')
678 parser.add_option('-V', '--prepend-version', dest='prepend_version',
679 default=None,
680 help='Add an identifier to the front of the version')
681 parser.add_option('-f', '--filters', dest='filters', action='store_true',
682 default=False,
683 help='Turn on filtering of private ebuild packages')
684 parser.add_option('-k', '--key', dest='key',
685 default='PORTAGE_BINHOST',
686 help='Key to update in make.conf / binhost.conf')
David James8ece7ee2011-06-29 16:02:30 -0700687 parser.add_option('', '--set-version', dest='set_version',
688 default=None,
689 help='Specify the version string')
David James8c846492011-01-25 17:07:29 -0800690 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
691 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800692 help='Update binhost.conf in chromiumos-overlay or '
693 'chromeos-overlay. Commit the changes, but don\'t '
694 'push them. This is used for preflight binhosts.')
David James32b0b2f2011-07-13 20:56:50 -0700695 parser.add_option('', '--binhost-conf-dir', dest='binhost_conf_dir',
696 default=_BINHOST_CONF_DIR,
697 help='Directory to commit binhost config with '
698 '--sync-binhost-conf.')
David Jamesfd0b0852011-02-23 11:15:36 -0800699 parser.add_option('-P', '--private', dest='private', action='store_true',
700 default=False, help='Mark gs:// uploads as private.')
David James8ece7ee2011-06-29 16:02:30 -0700701 parser.add_option('', '--skip-upload', dest='skip_upload',
702 action='store_true', default=False,
703 help='Skip upload step.')
David James8fa34ea2011-04-15 13:00:20 -0700704 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
705 action='store_true', default=False,
706 help='Upload board tarball to Google Storage.')
David James27fa7d12011-06-29 17:24:14 -0700707 parser.add_option('', '--debug', dest='debug',
708 action='store_true', default=False,
709 help='Don\'t push or upload prebuilts.')
David James8c846492011-01-25 17:07:29 -0800710
711 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800712 if not options.build_path:
713 usage(parser, 'Error: you need provide a chroot path')
David James8ece7ee2011-06-29 16:02:30 -0700714 if not options.upload and not options.skip_upload:
David James8c846492011-01-25 17:07:29 -0800715 usage(parser, 'Error: you need to provide an upload location using -u')
David James8ece7ee2011-06-29 16:02:30 -0700716 if not options.set_version and options.skip_upload:
717 usage(parser, 'Error: If you are using --skip-upload, you must specify a '
718 'version number using --set-version.')
David James9417f272011-05-26 13:24:47 -0700719 if args:
720 usage(parser, 'Error: invalid arguments passed to prebuilt.py: %r' % args)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700721
David James4058b0d2011-12-08 21:24:50 -0800722 options.target = BuildTarget(options.board, options.profile)
723 if options.target in options.slave_targets:
724 usage(parser, 'Error: --board/--profile must not also be a slave target.')
David Jamese2488642011-11-14 16:15:20 -0800725
David James4058b0d2011-12-08 21:24:50 -0800726 if len(set(options.slave_targets)) != len(options.slave_targets):
David Jamese2488642011-11-14 16:15:20 -0800727 usage(parser, 'Error: --slave-boards must not have duplicates.')
728
David James4058b0d2011-12-08 21:24:50 -0800729 if options.slave_targets and options.git_sync:
David Jamese2488642011-11-14 16:15:20 -0800730 usage(parser, 'Error: --slave-boards is not compatible with --git-sync')
731
David James8ece7ee2011-06-29 16:02:30 -0700732 if (options.upload_board_tarball and options.skip_upload and
733 options.board == 'amd64-host'):
734 usage(parser, 'Error: --skip-upload is not compatible with '
735 '--upload-board-tarball and --board=amd64-host')
David James8fa34ea2011-04-15 13:00:20 -0700736
David James8ece7ee2011-06-29 16:02:30 -0700737 if (options.upload_board_tarball and not options.skip_upload and
738 not options.upload.startswith('gs://')):
David James8fa34ea2011-04-15 13:00:20 -0700739 usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
740 '--upload must be a gs:// URL.')
741
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700742 if options.private:
743 if options.sync_host:
744 usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
745 'together, we do not support private host prebuilts')
746
David James8ece7ee2011-06-29 16:02:30 -0700747 if not options.upload or not options.upload.startswith('gs://'):
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700748 usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
749 '--upload must be a gs:// URL.')
750
751 if options.binhost_base_url != _BINHOST_BASE_URL:
David Jamese2488642011-11-14 16:15:20 -0800752 usage(parser, 'Error: when using --private the --binhost-base-url '
753 'is automatically derived.')
David James27fa7d12011-06-29 17:24:14 -0700754
David Jamesc0f158a2011-02-22 16:07:29 -0800755 return options
756
757def main():
David Jamesdb401072011-06-10 12:17:16 -0700758 # Set umask to a sane value so that files created as root are readable.
759 os.umask(022)
760
David Jamesc0f158a2011-02-22 16:07:29 -0800761 options = ParseOptions()
762
David James05bcb2b2011-02-09 09:25:47 -0800763 # Calculate a list of Packages index files to compare against. Whenever we
764 # upload a package, we check to make sure it's not already stored in one of
765 # the packages files we uploaded. This list of packages files might contain
766 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800767 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800768
David James8ece7ee2011-06-29 16:02:30 -0700769 if options.set_version:
770 version = options.set_version
771 else:
772 version = GetVersion()
David Jamesc0f158a2011-02-22 16:07:29 -0800773 if options.prepend_version:
774 version = '%s-%s' % (options.prepend_version, version)
775
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700776 acl = 'public-read'
777 binhost_base_url = options.binhost_base_url
778
779 if options.private:
780 binhost_base_url = options.upload
781 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
David James4058b0d2011-12-08 21:24:50 -0800782 options.target)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700783 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
784
785 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David James615e5b52011-06-03 11:10:15 -0700786 pkg_indexes, options.build_path,
David James27fa7d12011-06-29 17:24:14 -0700787 options.packages, options.skip_upload,
David Jamese2488642011-11-14 16:15:20 -0800788 options.binhost_conf_dir, options.debug,
David James4058b0d2011-12-08 21:24:50 -0800789 options.target, options.slave_targets)
David Jamesc0f158a2011-02-22 16:07:29 -0800790
David James8c846492011-01-25 17:07:29 -0800791 if options.sync_host:
David James615e5b52011-06-03 11:10:15 -0700792 uploader._SyncHostPrebuilts(version, options.key, options.git_sync,
793 options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800794
795 if options.board:
David Jamese2488642011-11-14 16:15:20 -0800796 uploader._SyncBoardPrebuilts(version, options.key, options.git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700797 options.sync_binhost_conf,
798 options.upload_board_tarball)
David James8c846492011-01-25 17:07:29 -0800799
800if __name__ == '__main__':
801 main()