blob: 0da1591e9ed94745254a3a25d945f61a812775b2 [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
10import re
11import sys
12import tempfile
David James8c846492011-01-25 17:07:29 -080013
Chris Sosa471532a2011-02-01 15:10:06 -080014if __name__ == '__main__':
15 import constants
16 sys.path.append(constants.SOURCE_ROOT)
17
David James8c846492011-01-25 17:07:29 -080018from chromite.lib import cros_build_lib
Chris Sosac13bba52011-05-24 15:14:09 -070019from chromite.lib.binpkg import (GrabLocalPackageIndex, GrabRemotePackageIndex)
David James8c846492011-01-25 17:07:29 -080020"""
21This script is used to upload host prebuilts as well as board BINHOSTS.
22
23If the URL starts with 'gs://', we upload using gsutil to Google Storage.
24Otherwise, rsync is used.
25
26After a build is successfully uploaded a file is updated with the proper
27BINHOST version as well as the target board. This file is defined in GIT_FILE
28
29
30To read more about prebuilts/binhost binary packages please refer to:
31http://sites/chromeos/for-team-members/engineering/releng/prebuilt-binaries-for-streamlining-the-build-process
32
33
34Example of uploading prebuilt amd64 host files to Google Storage:
35./prebuilt.py -p /b/cbuild/build -s -u gs://chromeos-prebuilt
36
37Example of uploading x86-dogfood binhosts to Google Storage:
38./prebuilt.py -b x86-dogfood -p /b/cbuild/build/ -u gs://chromeos-prebuilt -g
39
40Example of uploading prebuilt amd64 host files using rsync:
41./prebuilt.py -p /b/cbuild/build -s -u codf30.jail:/tmp
42"""
43
David James8c846492011-01-25 17:07:29 -080044_RETRIES = 3
45_GSUTIL_BIN = '/b/build/third_party/gsutil/gsutil'
46_HOST_PACKAGES_PATH = 'chroot/var/lib/portage/pkgs'
David James05bcb2b2011-02-09 09:25:47 -080047_CATEGORIES_PATH = 'chroot/etc/portage/categories'
David James615e5b52011-06-03 11:10:15 -070048_PYM_PATH = 'chroot/usr/lib/portage/pym'
David James8c846492011-01-25 17:07:29 -080049_HOST_TARGET = 'amd64'
50_BOARD_PATH = 'chroot/build/%(board)s'
David James8fa34ea2011-04-15 13:00:20 -070051# board/board-target/version/'
52_REL_BOARD_PATH = 'board/%(board)s/%(version)s'
53# host/host-target/version/'
David Jamese2488642011-11-14 16:15:20 -080054_REL_HOST_PATH = 'host/%(target)s/%(board)s/%(version)s'
David James8c846492011-01-25 17:07:29 -080055# Private overlays to look at for builds to filter
56# relative to build path
57_PRIVATE_OVERLAY_DIR = 'src/private-overlays'
Scott Zawalskiab1bed32011-03-16 15:24:24 -070058_GOOGLESTORAGE_ACL_FILE = 'googlestorage_acl.xml'
David Jamesce619292011-11-08 11:42:36 -080059_BINHOST_BASE_URL = 'https://commondatastorage.googleapis.com/chromeos-prebuilt'
David James8c846492011-01-25 17:07:29 -080060_PREBUILT_BASE_DIR = 'src/third_party/chromiumos-overlay/chromeos/config/'
61# Created in the event of new host targets becoming available
62_PREBUILT_MAKE_CONF = {'amd64': os.path.join(_PREBUILT_BASE_DIR,
63 'make.conf.amd64-host')}
64_BINHOST_CONF_DIR = 'src/third_party/chromiumos-overlay/chromeos/binhost'
65
66
David James8c846492011-01-25 17:07:29 -080067class UploadFailed(Exception):
68 """Raised when one of the files uploaded failed."""
69 pass
70
71class UnknownBoardFormat(Exception):
72 """Raised when a function finds an unknown board format."""
73 pass
74
David James8c846492011-01-25 17:07:29 -080075
76def UpdateLocalFile(filename, value, key='PORTAGE_BINHOST'):
77 """Update the key in file with the value passed.
78 File format:
79 key="value"
80 Note quotes are added automatically
81
82 Args:
83 filename: Name of file to modify.
84 value: Value to write with the key.
85 key: The variable key to update. (Default: PORTAGE_BINHOST)
86 """
87 if os.path.exists(filename):
88 file_fh = open(filename)
89 else:
90 file_fh = open(filename, 'w+')
91 file_lines = []
92 found = False
93 keyval_str = '%(key)s=%(value)s'
94 for line in file_fh:
95 # Strip newlines from end of line. We already add newlines below.
96 line = line.rstrip("\n")
97
98 if len(line.split('=')) != 2:
99 # Skip any line that doesn't fit key=val.
100 file_lines.append(line)
101 continue
102
103 file_var, file_val = line.split('=')
104 if file_var == key:
105 found = True
David Jamese2488642011-11-14 16:15:20 -0800106 print 'Updating %s=%s to %s=%s' % (file_var, file_val, key, value)
David James8c846492011-01-25 17:07:29 -0800107 value = '"%s"' % value
108 file_lines.append(keyval_str % {'key': key, 'value': value})
109 else:
110 file_lines.append(keyval_str % {'key': file_var, 'value': file_val})
111
112 if not found:
113 file_lines.append(keyval_str % {'key': key, 'value': value})
114
115 file_fh.close()
116 # write out new file
117 new_file_fh = open(filename, 'w')
118 new_file_fh.write('\n'.join(file_lines) + '\n')
119 new_file_fh.close()
120
121
David James27fa7d12011-06-29 17:24:14 -0700122def RevGitFile(filename, value, retries=5, key='PORTAGE_BINHOST', dryrun=False):
David James8c846492011-01-25 17:07:29 -0800123 """Update and push the git file.
124
125 Args:
126 filename: file to modify that is in a git repo already
127 value: string representing the version of the prebuilt that has been
128 uploaded.
129 retries: The number of times to retry before giving up, default: 5
130 key: The variable key to update in the git file.
131 (Default: PORTAGE_BINHOST)
132 """
133 prebuilt_branch = 'prebuilt_branch'
David James1b6e67a2011-05-19 21:32:38 -0700134 cwd = os.path.abspath(os.path.dirname(filename))
135 commit = cros_build_lib.RunCommand(['git', 'rev-parse', 'HEAD'], cwd=cwd,
Peter Mayofe0e6872011-04-20 00:52:08 -0400136 redirect_stdout=True).output.rstrip()
Peter Mayo193f68f2011-04-19 19:08:21 -0400137 git_ssh_config_cmd = [
138 'git',
139 'config',
Chris Sosac13bba52011-05-24 15:14:09 -0700140 'url.ssh://gerrit.chromium.org:29418.pushinsteadof',
David James1b6e67a2011-05-19 21:32:38 -0700141 'http://git.chromium.org']
142 cros_build_lib.RunCommand(git_ssh_config_cmd, cwd=cwd)
David Jamesbcfdc5d2011-08-25 20:46:33 -0700143 _RetryRun(['git', 'remote', 'update'], cwd=cwd)
David James1b6e67a2011-05-19 21:32:38 -0700144 cros_build_lib.RunCommand(['repo', 'start', prebuilt_branch, '.'], cwd=cwd)
David James6181b892011-06-08 16:45:07 -0700145
146 # We want to push our changes to this file to tip of tree, so we should
147 # make sure the branch is at tip of tree before we apply our change.
148 push_branch = '%s/%s' % cros_build_lib.GetPushBranch(prebuilt_branch, cwd)
149 cros_build_lib.RunCommand(['git', 'reset', '--hard', push_branch], cwd=cwd)
150
David James8c846492011-01-25 17:07:29 -0800151 description = 'Update %s="%s" in %s' % (key, value, filename)
152 print description
153 try:
154 UpdateLocalFile(filename, value, key)
David James1b6e67a2011-05-19 21:32:38 -0700155 cros_build_lib.RunCommand(['git', 'config', 'push.default', 'tracking'],
156 cwd=cwd)
157 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
158 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James27fa7d12011-06-29 17:24:14 -0700159 cros_build_lib.GitPushWithRetry(prebuilt_branch, cwd=cwd, dryrun=dryrun)
David James8c846492011-01-25 17:07:29 -0800160 finally:
David James1b6e67a2011-05-19 21:32:38 -0700161 cros_build_lib.RunCommand(['git', 'checkout', commit], cwd=cwd)
162 cros_build_lib.RunCommand(['repo', 'abandon', 'prebuilt_branch', '.'],
163 cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800164
165
166def GetVersion():
167 """Get the version to put in LATEST and update the git version with."""
168 return datetime.datetime.now().strftime('%d.%m.%y.%H%M%S')
169
170
Peter Mayo193f68f2011-04-19 19:08:21 -0400171def _RetryRun(cmd, print_cmd=True, cwd=None):
David James8c846492011-01-25 17:07:29 -0800172 """Run the specified command, retrying if necessary.
173
174 Args:
175 cmd: The command to run.
176 print_cmd: Whether to print out the cmd.
177 shell: Whether to treat the command as a shell.
178 cwd: Working directory to run command in.
179
180 Returns:
181 True if the command succeeded. Otherwise, returns False.
182 """
183
184 # TODO(scottz): port to use _Run or similar when it is available in
185 # cros_build_lib.
Chris Sosa58669192011-06-30 12:45:03 -0700186 for unused_attempt in range(_RETRIES):
David James8c846492011-01-25 17:07:29 -0800187 try:
Chris Sosa58669192011-06-30 12:45:03 -0700188 cros_build_lib.RunCommand(cmd, print_cmd=print_cmd, cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800189 return True
190 except cros_build_lib.RunCommandError:
Peter Mayo193f68f2011-04-19 19:08:21 -0400191 print 'Failed to run %r' % cmd
David James8c846492011-01-25 17:07:29 -0800192 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400193 print 'Retry failed run %r, giving up' % cmd
David James8c846492011-01-25 17:07:29 -0800194 return False
195
196
197def _GsUpload(args):
198 """Upload to GS bucket.
199
200 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800201 args: a tuple of three arguments that contains local_file, remote_file, and
202 the acl used for uploading the file.
David James8c846492011-01-25 17:07:29 -0800203
204 Returns:
205 Return the arg tuple of two if the upload failed
206 """
David Jamesfd0b0852011-02-23 11:15:36 -0800207 (local_file, remote_file, acl) = args
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700208 CANNED_ACLS = ['public-read', 'private', 'bucket-owner-read',
209 'authenticated-read', 'bucket-owner-full-control',
210 'public-read-write']
211 acl_cmd = None
212 if acl in CANNED_ACLS:
Peter Mayo193f68f2011-04-19 19:08:21 -0400213 cmd = [_GSUTIL_BIN, 'cp', '-a', acl, local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700214 else:
215 # For private uploads we assume that the overlay board is set up properly
216 # and a googlestore_acl.xml is present, if not this script errors
Peter Mayo193f68f2011-04-19 19:08:21 -0400217 cmd = [_GSUTIL_BIN, 'cp', '-a', 'private', local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700218 if not os.path.exists(acl):
219 print >> sys.stderr, ('You are specifying either a file that does not '
220 'exist or an unknown canned acl: %s. Aborting '
221 'upload') % acl
222 # emulate the failing of an upload since we are not uploading the file
223 return (local_file, remote_file)
David James8c846492011-01-25 17:07:29 -0800224
Peter Mayo193f68f2011-04-19 19:08:21 -0400225 acl_cmd = [_GSUTIL_BIN, 'setacl', acl, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700226
Peter Mayo193f68f2011-04-19 19:08:21 -0400227 if not _RetryRun(cmd, print_cmd=False):
David James8c846492011-01-25 17:07:29 -0800228 return (local_file, remote_file)
229
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700230 if acl_cmd:
231 # Apply the passed in ACL xml file to the uploaded object.
Peter Mayo193f68f2011-04-19 19:08:21 -0400232 _RetryRun(acl_cmd, print_cmd=False)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700233
234
David Jamesfd0b0852011-02-23 11:15:36 -0800235def RemoteUpload(acl, files, pool=10):
David James8c846492011-01-25 17:07:29 -0800236 """Upload to google storage.
237
238 Create a pool of process and call _GsUpload with the proper arguments.
239
240 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800241 acl: The canned acl used for uploading. acl can be one of: "public-read",
242 "public-read-write", "authenticated-read", "bucket-owner-read",
243 "bucket-owner-full-control", or "private".
David James8c846492011-01-25 17:07:29 -0800244 files: dictionary with keys to local files and values to remote path.
245 pool: integer of maximum proesses to have at the same time.
246
247 Returns:
248 Return a set of tuple arguments of the failed uploads
249 """
250 # TODO(scottz) port this to use _RunManyParallel when it is available in
251 # cros_build_lib
252 pool = multiprocessing.Pool(processes=pool)
253 workers = []
254 for local_file, remote_path in files.iteritems():
David Jamesfd0b0852011-02-23 11:15:36 -0800255 workers.append((local_file, remote_path, acl))
David James8c846492011-01-25 17:07:29 -0800256
257 result = pool.map_async(_GsUpload, workers, chunksize=1)
258 while True:
259 try:
Chris Sosa471532a2011-02-01 15:10:06 -0800260 return set(result.get(60 * 60))
David James8c846492011-01-25 17:07:29 -0800261 except multiprocessing.TimeoutError:
262 pass
263
264
265def GenerateUploadDict(base_local_path, base_remote_path, pkgs):
266 """Build a dictionary of local remote file key pairs to upload.
267
268 Args:
269 base_local_path: The base path to the files on the local hard drive.
270 remote_path: The base path to the remote paths.
271 pkgs: The packages to upload.
272
273 Returns:
274 Returns a dictionary of local_path/remote_path pairs
275 """
276 upload_files = {}
277 for pkg in pkgs:
278 suffix = pkg['CPV'] + '.tbz2'
279 local_path = os.path.join(base_local_path, suffix)
280 assert os.path.exists(local_path)
281 remote_path = '%s/%s' % (base_remote_path.rstrip('/'), suffix)
282 upload_files[local_path] = remote_path
283
284 return upload_files
285
286def GetBoardPathFromCrosOverlayList(build_path, target):
287 """Use the cros_overlay_list to determine the path to the board overlay
288 Args:
289 build_path: The path to the root of the build directory
290 target: The target that we are looking for, could consist of board and
291 board_variant, we handle that properly
292 Returns:
293 The last line from cros_overlay_list as a string
294 """
Chris Sosa471532a2011-02-01 15:10:06 -0800295 script_dir = os.path.join(build_path, 'src/platform/dev/host')
David James8c846492011-01-25 17:07:29 -0800296 cmd = ['./cros_overlay_list']
297 if re.match('.*?_.*', target):
298 (board, variant) = target.split('_')
299 cmd += ['--board', board, '--variant', variant]
David James8c846492011-01-25 17:07:29 -0800300 else:
Chris Sosa6c03c0f2011-07-29 09:23:06 -0700301 cmd += ['--board', target]
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 """
320 if _HOST_TARGET == target:
321 # 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 James8c846492011-01-25 17:07:29 -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 Jamese2488642011-11-14 16:15:20 -0800379 binhost_conf_dir, debug, board, slave_boards):
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 Jamese2488642011-11-14 16:15:20 -0800398 board: Boards managed by this builder.
399 slave_boards: Boards 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 Jamese2488642011-11-14 16:15:20 -0800410 self._board = board
411 self._slave_boards = slave_boards
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))
417 sys.path.append(pym_path)
418 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)
481 cmd = ['sudo', 'tar', '-I', 'pbzip2', '-cf', tarfile]
482 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('.')
487 cros_build_lib.RunCommand(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:
499 cros_build_lib.RunCommand(['sudo', 'rm', '-rf', tmpdir], cwd=cwd)
500
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 = []
525 for board in self._slave_boards + [self._board]:
526 url_suffix = _REL_HOST_PATH % {'version': version, 'target': _HOST_TARGET,
527 'board': board}
528 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James05bcb2b2011-02-09 09:25:47 -0800529
David Jamese2488642011-11-14 16:15:20 -0800530 if board == self._board and not self._skip_upload and not self._debug:
531 # Upload prebuilts.
532 package_path = os.path.join(self._build_path, _HOST_PACKAGES_PATH)
533 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8ece7ee2011-06-29 16:02:30 -0700534
David Jamese2488642011-11-14 16:15:20 -0800535 # Record URL where prebuilts were uploaded.
536 binhost_urls.append('%s/%s/' % (self._binhost_base_url.rstrip('/'),
537 packages_url_suffix.rstrip('/')))
538
539 binhost = '"%s"' % ' '.join(binhost_urls)
David James8ece7ee2011-06-29 16:02:30 -0700540 if git_sync:
541 git_file = os.path.join(self._build_path,
542 _PREBUILT_MAKE_CONF[_HOST_TARGET])
David Jamese2488642011-11-14 16:15:20 -0800543 RevGitFile(git_file, binhost, key=key, dryrun=self._debug)
David James8ece7ee2011-06-29 16:02:30 -0700544 if sync_binhost_conf:
David James32b0b2f2011-07-13 20:56:50 -0700545 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James8ece7ee2011-06-29 16:02:30 -0700546 'host', '%s-%s.conf' % (_HOST_TARGET, key))
David Jamese2488642011-11-14 16:15:20 -0800547 UpdateBinhostConfFile(binhost_conf, key, binhost)
David Jamesc0f158a2011-02-22 16:07:29 -0800548
David Jamese2488642011-11-14 16:15:20 -0800549 def _SyncBoardPrebuilts(self, version, key, git_sync, sync_binhost_conf,
550 upload_board_tarball):
David Jamesc0f158a2011-02-22 16:07:29 -0800551 """Synchronize board prebuilt files.
552
553 Args:
554 board: The board to upload to Google Storage.
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 Jamese2488642011-11-14 16:15:20 -0800564 for board in self._slave_boards + [self._board]:
565 board_path = os.path.join(self._build_path,
566 _BOARD_PATH % {'board': board})
567 package_path = os.path.join(board_path, 'packages')
568 url_suffix = _REL_BOARD_PATH % {'board': board, 'version': version}
569 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James8fa34ea2011-04-15 13:00:20 -0700570
David Jamese2488642011-11-14 16:15:20 -0800571 if board == self._board and not self._skip_upload and not self._debug:
572 # 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.
587 if board == 'amd64-host':
588 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:
598 git_file = DeterminePrebuiltConfFile(self._build_path, board)
599 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,
602 'target', '%s-%s.conf' % (board, key))
603 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 Jamesc0f158a2011-02-22 16:07:29 -0800612def ParseOptions():
David James8c846492011-01-25 17:07:29 -0800613 parser = optparse.OptionParser()
614 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
615 default=_BINHOST_BASE_URL,
616 help='Base URL to use for binhost in make.conf updates')
617 parser.add_option('', '--previous-binhost-url', action='append',
618 default=[], dest='previous_binhost_url',
619 help='Previous binhost URL')
620 parser.add_option('-b', '--board', dest='board', default=None,
621 help='Board type that was built on this machine')
David Jamese2488642011-11-14 16:15:20 -0800622 parser.add_option('', '--slave-board', action='append', default=[],
623 dest='slave_boards',
624 help='Board type that was built on a slave machine')
David James8c846492011-01-25 17:07:29 -0800625 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800626 help='Path to the directory containing the chroot')
David James615e5b52011-06-03 11:10:15 -0700627 parser.add_option('', '--packages', action='append',
628 default=[], dest='packages',
629 help='Only include the specified packages. '
630 '(Default is to include all packages.)')
David James8c846492011-01-25 17:07:29 -0800631 parser.add_option('-s', '--sync-host', dest='sync_host',
632 default=False, action='store_true',
633 help='Sync host prebuilts')
634 parser.add_option('-g', '--git-sync', dest='git_sync',
635 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800636 help='Enable git version sync (This commits to a repo.) '
637 'This is used by full builders to commit directly '
638 'to board overlays.')
David James8c846492011-01-25 17:07:29 -0800639 parser.add_option('-u', '--upload', dest='upload',
640 default=None,
641 help='Upload location')
642 parser.add_option('-V', '--prepend-version', dest='prepend_version',
643 default=None,
644 help='Add an identifier to the front of the version')
645 parser.add_option('-f', '--filters', dest='filters', action='store_true',
646 default=False,
647 help='Turn on filtering of private ebuild packages')
648 parser.add_option('-k', '--key', dest='key',
649 default='PORTAGE_BINHOST',
650 help='Key to update in make.conf / binhost.conf')
David James8ece7ee2011-06-29 16:02:30 -0700651 parser.add_option('', '--set-version', dest='set_version',
652 default=None,
653 help='Specify the version string')
David James8c846492011-01-25 17:07:29 -0800654 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
655 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800656 help='Update binhost.conf in chromiumos-overlay or '
657 'chromeos-overlay. Commit the changes, but don\'t '
658 'push them. This is used for preflight binhosts.')
David James32b0b2f2011-07-13 20:56:50 -0700659 parser.add_option('', '--binhost-conf-dir', dest='binhost_conf_dir',
660 default=_BINHOST_CONF_DIR,
661 help='Directory to commit binhost config with '
662 '--sync-binhost-conf.')
David Jamesfd0b0852011-02-23 11:15:36 -0800663 parser.add_option('-P', '--private', dest='private', action='store_true',
664 default=False, help='Mark gs:// uploads as private.')
David James8ece7ee2011-06-29 16:02:30 -0700665 parser.add_option('', '--skip-upload', dest='skip_upload',
666 action='store_true', default=False,
667 help='Skip upload step.')
David James8fa34ea2011-04-15 13:00:20 -0700668 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
669 action='store_true', default=False,
670 help='Upload board tarball to Google Storage.')
David James27fa7d12011-06-29 17:24:14 -0700671 parser.add_option('', '--debug', dest='debug',
672 action='store_true', default=False,
673 help='Don\'t push or upload prebuilts.')
David James8c846492011-01-25 17:07:29 -0800674
675 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800676 if not options.build_path:
677 usage(parser, 'Error: you need provide a chroot path')
David James8ece7ee2011-06-29 16:02:30 -0700678 if not options.upload and not options.skip_upload:
David James8c846492011-01-25 17:07:29 -0800679 usage(parser, 'Error: you need to provide an upload location using -u')
David James8ece7ee2011-06-29 16:02:30 -0700680 if not options.set_version and options.skip_upload:
681 usage(parser, 'Error: If you are using --skip-upload, you must specify a '
682 'version number using --set-version.')
David James9417f272011-05-26 13:24:47 -0700683 if args:
684 usage(parser, 'Error: invalid arguments passed to prebuilt.py: %r' % args)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700685
David Jamese2488642011-11-14 16:15:20 -0800686 if options.board in options.slave_boards:
687 usage(parser, 'Error: --board must not also be a slave board.')
688
689 if len(set(options.slave_boards)) != len(options.slave_boards):
690 usage(parser, 'Error: --slave-boards must not have duplicates.')
691
692 if options.slave_boards and options.git_sync:
693 usage(parser, 'Error: --slave-boards is not compatible with --git-sync')
694
David James8ece7ee2011-06-29 16:02:30 -0700695 if (options.upload_board_tarball and options.skip_upload and
696 options.board == 'amd64-host'):
697 usage(parser, 'Error: --skip-upload is not compatible with '
698 '--upload-board-tarball and --board=amd64-host')
David James8fa34ea2011-04-15 13:00:20 -0700699
David James8ece7ee2011-06-29 16:02:30 -0700700 if (options.upload_board_tarball and not options.skip_upload and
701 not options.upload.startswith('gs://')):
David James8fa34ea2011-04-15 13:00:20 -0700702 usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
703 '--upload must be a gs:// URL.')
704
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700705 if options.private:
706 if options.sync_host:
707 usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
708 'together, we do not support private host prebuilts')
709
David James8ece7ee2011-06-29 16:02:30 -0700710 if not options.upload or not options.upload.startswith('gs://'):
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700711 usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
712 '--upload must be a gs:// URL.')
713
714 if options.binhost_base_url != _BINHOST_BASE_URL:
David Jamese2488642011-11-14 16:15:20 -0800715 usage(parser, 'Error: when using --private the --binhost-base-url '
716 'is automatically derived.')
David James27fa7d12011-06-29 17:24:14 -0700717
David Jamesc0f158a2011-02-22 16:07:29 -0800718 return options
719
720def main():
David Jamesdb401072011-06-10 12:17:16 -0700721 # Set umask to a sane value so that files created as root are readable.
722 os.umask(022)
723
David Jamesc0f158a2011-02-22 16:07:29 -0800724 options = ParseOptions()
725
David James05bcb2b2011-02-09 09:25:47 -0800726 # Calculate a list of Packages index files to compare against. Whenever we
727 # upload a package, we check to make sure it's not already stored in one of
728 # the packages files we uploaded. This list of packages files might contain
729 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800730 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800731
David James8ece7ee2011-06-29 16:02:30 -0700732 if options.set_version:
733 version = options.set_version
734 else:
735 version = GetVersion()
David Jamesc0f158a2011-02-22 16:07:29 -0800736 if options.prepend_version:
737 version = '%s-%s' % (options.prepend_version, version)
738
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700739 acl = 'public-read'
740 binhost_base_url = options.binhost_base_url
741
742 if options.private:
743 binhost_base_url = options.upload
744 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
745 options.board)
746 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
747
748 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David James615e5b52011-06-03 11:10:15 -0700749 pkg_indexes, options.build_path,
David James27fa7d12011-06-29 17:24:14 -0700750 options.packages, options.skip_upload,
David Jamese2488642011-11-14 16:15:20 -0800751 options.binhost_conf_dir, options.debug,
752 options.board, options.slave_boards)
David Jamesc0f158a2011-02-22 16:07:29 -0800753
David James8c846492011-01-25 17:07:29 -0800754 if options.sync_host:
David James615e5b52011-06-03 11:10:15 -0700755 uploader._SyncHostPrebuilts(version, options.key, options.git_sync,
756 options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800757
758 if options.board:
David Jamese2488642011-11-14 16:15:20 -0800759 uploader._SyncBoardPrebuilts(version, options.key, options.git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700760 options.sync_binhost_conf,
761 options.upload_board_tarball)
David James8c846492011-01-25 17:07:29 -0800762
763if __name__ == '__main__':
764 main()