blob: 2b83dbefc05495cf5c860eb64dc0a1fe64107cb5 [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/'
54_REL_HOST_PATH = 'host/%(target)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 James8c846492011-01-25 17:07:29 -080059_BINHOST_BASE_URL = 'http://commondatastorage.googleapis.com/chromeos-prebuilt'
60_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
106 print 'Updating %s=%s to %s="%s"' % (file_var, file_val, key, value)
107 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)
143 cros_build_lib.RunCommand(['git', 'remote', 'update'], cwd=cwd)
144 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]
300 elif re.match('.*?-\w+', target):
301 cmd += ['--board', target]
Scott Zawalski1c660ea2011-06-24 19:12:58 -0700302 elif target == 'stumpy':
303 cmd += ['--board', target]
David James8c846492011-01-25 17:07:29 -0800304 else:
305 raise UnknownBoardFormat('Unknown format: %s' % target)
306
307 cmd_output = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
308 cwd=script_dir)
309 # We only care about the last entry
310 return cmd_output.output.splitlines().pop()
311
312
313def DeterminePrebuiltConfFile(build_path, target):
314 """Determine the prebuilt.conf file that needs to be updated for prebuilts.
315
316 Args:
317 build_path: The path to the root of the build directory
318 target: String representation of the board. This includes host and board
319 targets
320
321 Returns
322 A string path to a prebuilt.conf file to be updated.
323 """
324 if _HOST_TARGET == target:
325 # We are host.
326 # Without more examples of hosts this is a kludge for now.
327 # TODO(Scottz): as new host targets come online expand this to
328 # work more like boards.
Chris Sosa471532a2011-02-01 15:10:06 -0800329 make_path = _PREBUILT_MAKE_CONF[target]
David James8c846492011-01-25 17:07:29 -0800330 else:
331 # We are a board
332 board = GetBoardPathFromCrosOverlayList(build_path, target)
333 make_path = os.path.join(board, 'prebuilt.conf')
334
335 return make_path
336
337
338def UpdateBinhostConfFile(path, key, value):
339 """Update binhost config file file with key=value.
340
341 Args:
342 path: Filename to update.
343 key: Key to update.
344 value: New value for key.
345 """
346 cwd = os.path.dirname(os.path.abspath(path))
347 filename = os.path.basename(path)
348 if not os.path.isdir(cwd):
349 os.makedirs(cwd)
350 if not os.path.isfile(path):
351 config_file = file(path, 'w')
David James8c846492011-01-25 17:07:29 -0800352 config_file.close()
353 UpdateLocalFile(path, value, key)
Chris Sosac13bba52011-05-24 15:14:09 -0700354 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800355 description = 'Update %s=%s in %s' % (key, value, filename)
Peter Mayo193f68f2011-04-19 19:08:21 -0400356 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800357
358
David Jamesce093af2011-02-23 15:21:58 -0800359def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800360 """Grab all of the packages files associated with a list of binhost_urls.
361
David James05bcb2b2011-02-09 09:25:47 -0800362 Args:
363 binhost_urls: The URLs for the directories containing the Packages files we
364 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800365
366 Returns:
367 A list of PackageIndex objects.
368 """
369 pkg_indexes = []
370 for url in binhost_urls:
371 pkg_index = GrabRemotePackageIndex(url)
372 if pkg_index:
373 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800374 return pkg_indexes
375
376
David James05bcb2b2011-02-09 09:25:47 -0800377
David Jamesc0f158a2011-02-22 16:07:29 -0800378class PrebuiltUploader(object):
379 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800380
David James615e5b52011-06-03 11:10:15 -0700381 def __init__(self, upload_location, acl, binhost_base_url,
David James27fa7d12011-06-29 17:24:14 -0700382 pkg_indexes, build_path, packages, skip_upload, debug):
David Jamesc0f158a2011-02-22 16:07:29 -0800383 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800384
David Jamesc0f158a2011-02-22 16:07:29 -0800385 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800386
David Jamesc0f158a2011-02-22 16:07:29 -0800387 Args:
388 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800389 acl: The canned acl used for uploading to Google Storage. acl can be one
390 of: "public-read", "public-read-write", "authenticated-read",
391 "bucket-owner-read", "bucket-owner-full-control", or "private". If
392 we are not uploading to Google Storage, this parameter is unused.
393 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800394 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
395 uploading duplicate files, we just link to the old files.
David James615e5b52011-06-03 11:10:15 -0700396 build_path: The path to the directory containing the chroot.
397 packages: Packages to upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800398 """
399 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800400 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800401 self._binhost_base_url = binhost_base_url
402 self._pkg_indexes = pkg_indexes
David James615e5b52011-06-03 11:10:15 -0700403 self._build_path = build_path
404 self._packages = set(packages)
David James8ece7ee2011-06-29 16:02:30 -0700405 self._skip_upload = skip_upload
David James27fa7d12011-06-29 17:24:14 -0700406 self._debug = debug
David James615e5b52011-06-03 11:10:15 -0700407
408 def _ShouldFilterPackage(self, pkg):
409 if not self._packages:
410 return False
411 pym_path = os.path.abspath(os.path.join(self._build_path, _PYM_PATH))
412 sys.path.append(pym_path)
413 import portage.versions
414 cat, pkgname = portage.versions.catpkgsplit(pkg['CPV'])[0:2]
415 cp = '%s/%s' % (cat, pkgname)
416 return pkgname not in self._packages and cp not in self._packages
David James8c846492011-01-25 17:07:29 -0800417
David Jamesc0f158a2011-02-22 16:07:29 -0800418 def _UploadPrebuilt(self, package_path, url_suffix):
419 """Upload host or board prebuilt files to Google Storage space.
David James8c846492011-01-25 17:07:29 -0800420
David Jamesc0f158a2011-02-22 16:07:29 -0800421 Args:
422 package_path: The path to the packages dir.
David Jamesce093af2011-02-23 15:21:58 -0800423 url_suffix: The remote subdirectory where we should upload the packages.
David Jamesa3bba142011-05-26 21:24:20 -0700424
David Jamesc0f158a2011-02-22 16:07:29 -0800425 """
David James8c846492011-01-25 17:07:29 -0800426
David Jamesc0f158a2011-02-22 16:07:29 -0800427 # Process Packages file, removing duplicates and filtered packages.
428 pkg_index = GrabLocalPackageIndex(package_path)
429 pkg_index.SetUploadLocation(self._binhost_base_url, url_suffix)
David James615e5b52011-06-03 11:10:15 -0700430 pkg_index.RemoveFilteredPackages(self._ShouldFilterPackage)
David Jamesc0f158a2011-02-22 16:07:29 -0800431 uploads = pkg_index.ResolveDuplicateUploads(self._pkg_indexes)
David James05bcb2b2011-02-09 09:25:47 -0800432
David Jamesc0f158a2011-02-22 16:07:29 -0800433 # Write Packages file.
434 tmp_packages_file = pkg_index.WriteToNamedTemporaryFile()
David James05bcb2b2011-02-09 09:25:47 -0800435
David Jamesc0f158a2011-02-22 16:07:29 -0800436 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
437 if remote_location.startswith('gs://'):
438 # Build list of files to upload.
439 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
440 remote_file = '%s/Packages' % remote_location.rstrip('/')
441 upload_files[tmp_packages_file.name] = remote_file
David James05bcb2b2011-02-09 09:25:47 -0800442
David Jamesfd0b0852011-02-23 11:15:36 -0800443 failed_uploads = RemoteUpload(self._acl, upload_files)
David Jamesc0f158a2011-02-22 16:07:29 -0800444 if len(failed_uploads) > 1 or (None not in failed_uploads):
David Jamesf4db1122011-03-17 16:18:05 -0700445 error_msg = ['%s -> %s\n' % args for args in failed_uploads if args]
David Jamesc0f158a2011-02-22 16:07:29 -0800446 raise UploadFailed('Error uploading:\n%s' % error_msg)
447 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400448 pkgs = [p['CPV'] + '.tbz2' for p in uploads]
David Jamesc0f158a2011-02-22 16:07:29 -0800449 ssh_server, remote_path = remote_location.split(':', 1)
Peter Mayo193f68f2011-04-19 19:08:21 -0400450 remote_path = remote_path.rstrip('/')
451 pkg_index = tmp_packages_file.name
452 remote_location = remote_location.rstrip('/')
453 remote_packages = '%s/Packages' % remote_location
454 cmds = [['ssh', ssh_server, 'mkdir', '-p', remote_path],
455 ['rsync', '-av', '--chmod=a+r', pkg_index, remote_packages]]
David Jamesc0f158a2011-02-22 16:07:29 -0800456 if pkgs:
Peter Mayo193f68f2011-04-19 19:08:21 -0400457 cmds.append(['rsync', '-Rav'] + pkgs + [remote_location + '/'])
David Jamesc0f158a2011-02-22 16:07:29 -0800458 for cmd in cmds:
Peter Mayo193f68f2011-04-19 19:08:21 -0400459 if not _RetryRun(cmd, cwd=package_path):
460 raise UploadFailed('Could not run %r' % cmd)
David James8c846492011-01-25 17:07:29 -0800461
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200462 def _UploadBoardTarball(self, board_path, url_suffix, version):
David James8fa34ea2011-04-15 13:00:20 -0700463 """Upload a tarball of the board at the specified path to Google Storage.
464
465 Args:
466 board_path: The path to the board dir.
467 url_suffix: The remote subdirectory where we should upload the packages.
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200468 version: The version of the board.
David James8fa34ea2011-04-15 13:00:20 -0700469 """
470 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
471 assert remote_location.startswith('gs://')
472 cwd, boardname = os.path.split(board_path.rstrip(os.path.sep))
473 tmpdir = tempfile.mkdtemp()
474 try:
475 tarfile = os.path.join(tmpdir, '%s.tbz2' % boardname)
476 cmd = ['sudo', 'tar', '-I', 'pbzip2', '-cf', tarfile]
477 excluded_paths = ('usr/lib/debug', 'usr/local/autotest', 'packages',
478 'tmp')
479 for path in excluded_paths:
Zdenek Behane3ed3462011-06-16 00:36:08 +0200480 cmd.append('--exclude=%s/*' % path)
481 cmd.append('.')
482 cros_build_lib.RunCommand(cmd, cwd=os.path.join(cwd, boardname))
David James8fa34ea2011-04-15 13:00:20 -0700483 remote_tarfile = '%s/%s.tbz2' % (remote_location.rstrip('/'), boardname)
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200484 # FIXME(zbehan): Temporary hack to upload amd64-host chroots to a
485 # different gs bucket. The right way is to do the upload in a separate
486 # pass of this script.
487 if boardname == 'amd64-host':
Zdenek Behanaf3c9002011-06-24 10:07:40 +0200488 # FIXME(zbehan): Why does version contain the prefix "chroot-"?
489 remote_tarfile = \
490 'gs://chromiumos-sdk/cros-sdk-%s.tbz2' % version.strip('chroot-')
David James8fa34ea2011-04-15 13:00:20 -0700491 if _GsUpload((tarfile, remote_tarfile, self._acl)):
492 sys.exit(1)
493 finally:
494 cros_build_lib.RunCommand(['sudo', 'rm', '-rf', tmpdir], cwd=cwd)
495
David James615e5b52011-06-03 11:10:15 -0700496 def _SyncHostPrebuilts(self, version, key, git_sync, sync_binhost_conf):
David Jamesc0f158a2011-02-22 16:07:29 -0800497 """Synchronize host prebuilt files.
David James05bcb2b2011-02-09 09:25:47 -0800498
David Jamesc0f158a2011-02-22 16:07:29 -0800499 This function will sync both the standard host packages, plus the host
500 packages associated with all targets that have been "setup" with the
501 current host's chroot. For instance, if this host has been used to build
502 x86-generic, it will sync the host packages associated with
503 'i686-pc-linux-gnu'. If this host has also been used to build arm-generic,
504 it will also sync the host packages associated with
505 'armv7a-cros-linux-gnueabi'.
David James05bcb2b2011-02-09 09:25:47 -0800506
David Jamesc0f158a2011-02-22 16:07:29 -0800507 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800508 version: A unique string, intended to be included in the upload path,
509 which identifies the version number of the uploaded prebuilts.
510 key: The variable key to update in the git file.
511 git_sync: If set, update make.conf of target to reference the latest
512 prebuilt packages generated here.
513 sync_binhost_conf: If set, update binhost config file in
514 chromiumos-overlay for the host.
515 """
516 # Upload prebuilts.
David James615e5b52011-06-03 11:10:15 -0700517 package_path = os.path.join(self._build_path, _HOST_PACKAGES_PATH)
David Jamesc0f158a2011-02-22 16:07:29 -0800518 url_suffix = _REL_HOST_PATH % {'version': version, 'target': _HOST_TARGET}
David James8fa34ea2011-04-15 13:00:20 -0700519 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James05bcb2b2011-02-09 09:25:47 -0800520
David James27fa7d12011-06-29 17:24:14 -0700521 if not self._skip_upload and not self._debug:
David James8ece7ee2011-06-29 16:02:30 -0700522 self._UploadPrebuilt(package_path, packages_url_suffix)
523
524 # Record URL where prebuilts were uploaded.
525 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
526 packages_url_suffix.rstrip('/'))
527 if git_sync:
528 git_file = os.path.join(self._build_path,
529 _PREBUILT_MAKE_CONF[_HOST_TARGET])
David James27fa7d12011-06-29 17:24:14 -0700530 RevGitFile(git_file, url_value, key=key, dryrun=self._debug)
David James8ece7ee2011-06-29 16:02:30 -0700531 if sync_binhost_conf:
532 binhost_conf = os.path.join(self._build_path, _BINHOST_CONF_DIR,
533 'host', '%s-%s.conf' % (_HOST_TARGET, key))
534 UpdateBinhostConfFile(binhost_conf, key, url_value)
David Jamesc0f158a2011-02-22 16:07:29 -0800535
David James615e5b52011-06-03 11:10:15 -0700536 def _SyncBoardPrebuilts(self, board, version, key, git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700537 sync_binhost_conf, upload_board_tarball):
David Jamesc0f158a2011-02-22 16:07:29 -0800538 """Synchronize board prebuilt files.
539
540 Args:
541 board: The board to upload to Google Storage.
David Jamesc0f158a2011-02-22 16:07:29 -0800542 version: A unique string, intended to be included in the upload path,
543 which identifies the version number of the uploaded prebuilts.
544 key: The variable key to update in the git file.
545 git_sync: If set, update make.conf of target to reference the latest
546 prebuilt packages generated here.
547 sync_binhost_conf: If set, update binhost config file in
548 chromiumos-overlay for the current board.
David James8fa34ea2011-04-15 13:00:20 -0700549 upload_board_tarball: Include a tarball of the board in our upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800550 """
David James615e5b52011-06-03 11:10:15 -0700551 board_path = os.path.join(self._build_path, _BOARD_PATH % {'board': board})
David Jamesc0f158a2011-02-22 16:07:29 -0800552 package_path = os.path.join(board_path, 'packages')
553 url_suffix = _REL_BOARD_PATH % {'board': board, 'version': version}
David James8fa34ea2011-04-15 13:00:20 -0700554 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
555
David James27fa7d12011-06-29 17:24:14 -0700556 if not self._skip_upload and not self._debug:
David James8ece7ee2011-06-29 16:02:30 -0700557 # Upload board tarballs in the background.
558 if upload_board_tarball:
559 tar_process = multiprocessing.Process(target=self._UploadBoardTarball,
560 args=(board_path, url_suffix,
561 version))
562 tar_process.start()
David James8fa34ea2011-04-15 13:00:20 -0700563
David James8ece7ee2011-06-29 16:02:30 -0700564 # Upload prebuilts.
565 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8fa34ea2011-04-15 13:00:20 -0700566
David James8ece7ee2011-06-29 16:02:30 -0700567 # Make sure we finished uploading the board tarballs.
568 if upload_board_tarball:
569 tar_process.join()
570 assert tar_process.exitcode == 0
571 # TODO(zbehan): This should be done cleaner.
572 if board == 'amd64-host':
573 sdk_conf = os.path.join(self._build_path, _BINHOST_CONF_DIR,
574 'host/sdk_version.conf')
575 RevGitFile(sdk_conf, version.strip('chroot-'),
David James27fa7d12011-06-29 17:24:14 -0700576 key='SDK_LATEST_VERSION', dryrun=self._debug)
David Jamesc0f158a2011-02-22 16:07:29 -0800577
David James8ece7ee2011-06-29 16:02:30 -0700578 # Record URL where prebuilts were uploaded.
579 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
580 packages_url_suffix.rstrip('/'))
581 if git_sync:
582 git_file = DeterminePrebuiltConfFile(self._build_path, board)
David James27fa7d12011-06-29 17:24:14 -0700583 RevGitFile(git_file, url_value, key=key, dryrun=self._debug)
David James8ece7ee2011-06-29 16:02:30 -0700584 if sync_binhost_conf:
585 binhost_conf = os.path.join(self._build_path, _BINHOST_CONF_DIR,
586 'target', '%s-%s.conf' % (board, key))
587 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800588
589
David James8c846492011-01-25 17:07:29 -0800590def usage(parser, msg):
591 """Display usage message and parser help then exit with 1."""
592 print >> sys.stderr, msg
593 parser.print_help()
594 sys.exit(1)
595
David Jamesc0f158a2011-02-22 16:07:29 -0800596def ParseOptions():
David James8c846492011-01-25 17:07:29 -0800597 parser = optparse.OptionParser()
598 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
599 default=_BINHOST_BASE_URL,
600 help='Base URL to use for binhost in make.conf updates')
601 parser.add_option('', '--previous-binhost-url', action='append',
602 default=[], dest='previous_binhost_url',
603 help='Previous binhost URL')
604 parser.add_option('-b', '--board', dest='board', default=None,
605 help='Board type that was built on this machine')
606 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800607 help='Path to the directory containing the chroot')
David James615e5b52011-06-03 11:10:15 -0700608 parser.add_option('', '--packages', action='append',
609 default=[], dest='packages',
610 help='Only include the specified packages. '
611 '(Default is to include all packages.)')
David James8c846492011-01-25 17:07:29 -0800612 parser.add_option('-s', '--sync-host', dest='sync_host',
613 default=False, action='store_true',
614 help='Sync host prebuilts')
615 parser.add_option('-g', '--git-sync', dest='git_sync',
616 default=False, action='store_true',
617 help='Enable git version sync (This commits to a repo)')
618 parser.add_option('-u', '--upload', dest='upload',
619 default=None,
620 help='Upload location')
621 parser.add_option('-V', '--prepend-version', dest='prepend_version',
622 default=None,
623 help='Add an identifier to the front of the version')
624 parser.add_option('-f', '--filters', dest='filters', action='store_true',
625 default=False,
626 help='Turn on filtering of private ebuild packages')
627 parser.add_option('-k', '--key', dest='key',
628 default='PORTAGE_BINHOST',
629 help='Key to update in make.conf / binhost.conf')
David James8ece7ee2011-06-29 16:02:30 -0700630 parser.add_option('', '--set-version', dest='set_version',
631 default=None,
632 help='Specify the version string')
David James8c846492011-01-25 17:07:29 -0800633 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
634 default=False, action='store_true',
635 help='Update binhost.conf')
David Jamesfd0b0852011-02-23 11:15:36 -0800636 parser.add_option('-P', '--private', dest='private', action='store_true',
637 default=False, help='Mark gs:// uploads as private.')
David James8ece7ee2011-06-29 16:02:30 -0700638 parser.add_option('', '--skip-upload', dest='skip_upload',
639 action='store_true', default=False,
640 help='Skip upload step.')
David James8fa34ea2011-04-15 13:00:20 -0700641 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
642 action='store_true', default=False,
643 help='Upload board tarball to Google Storage.')
David James27fa7d12011-06-29 17:24:14 -0700644 parser.add_option('', '--debug', dest='debug',
645 action='store_true', default=False,
646 help='Don\'t push or upload prebuilts.')
David James8c846492011-01-25 17:07:29 -0800647
648 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800649 if not options.build_path:
650 usage(parser, 'Error: you need provide a chroot path')
David James8ece7ee2011-06-29 16:02:30 -0700651 if not options.upload and not options.skip_upload:
David James8c846492011-01-25 17:07:29 -0800652 usage(parser, 'Error: you need to provide an upload location using -u')
David James8ece7ee2011-06-29 16:02:30 -0700653 if not options.set_version and options.skip_upload:
654 usage(parser, 'Error: If you are using --skip-upload, you must specify a '
655 'version number using --set-version.')
David James9417f272011-05-26 13:24:47 -0700656 if args:
657 usage(parser, 'Error: invalid arguments passed to prebuilt.py: %r' % args)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700658
David James8ece7ee2011-06-29 16:02:30 -0700659 if (options.upload_board_tarball and options.skip_upload and
660 options.board == 'amd64-host'):
661 usage(parser, 'Error: --skip-upload is not compatible with '
662 '--upload-board-tarball and --board=amd64-host')
David James8fa34ea2011-04-15 13:00:20 -0700663
David James8ece7ee2011-06-29 16:02:30 -0700664 if (options.upload_board_tarball and not options.skip_upload and
665 not options.upload.startswith('gs://')):
David James8fa34ea2011-04-15 13:00:20 -0700666 usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
667 '--upload must be a gs:// URL.')
668
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700669 if options.private:
670 if options.sync_host:
671 usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
672 'together, we do not support private host prebuilts')
673
David James8ece7ee2011-06-29 16:02:30 -0700674 if not options.upload or not options.upload.startswith('gs://'):
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700675 usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
676 '--upload must be a gs:// URL.')
677
678 if options.binhost_base_url != _BINHOST_BASE_URL:
679 usage(parser, 'Error: when using --private the --binhost-base-url '
680 'is automatically derived.')
David James27fa7d12011-06-29 17:24:14 -0700681
David Jamesc0f158a2011-02-22 16:07:29 -0800682 return options
683
684def main():
David Jamesdb401072011-06-10 12:17:16 -0700685 # Set umask to a sane value so that files created as root are readable.
686 os.umask(022)
687
David Jamesc0f158a2011-02-22 16:07:29 -0800688 options = ParseOptions()
689
David James05bcb2b2011-02-09 09:25:47 -0800690 # Calculate a list of Packages index files to compare against. Whenever we
691 # upload a package, we check to make sure it's not already stored in one of
692 # the packages files we uploaded. This list of packages files might contain
693 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800694 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800695
David James8ece7ee2011-06-29 16:02:30 -0700696 if options.set_version:
697 version = options.set_version
698 else:
699 version = GetVersion()
David Jamesc0f158a2011-02-22 16:07:29 -0800700 if options.prepend_version:
701 version = '%s-%s' % (options.prepend_version, version)
702
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700703 acl = 'public-read'
704 binhost_base_url = options.binhost_base_url
705
706 if options.private:
707 binhost_base_url = options.upload
708 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
709 options.board)
710 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
711
712 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David James615e5b52011-06-03 11:10:15 -0700713 pkg_indexes, options.build_path,
David James27fa7d12011-06-29 17:24:14 -0700714 options.packages, options.skip_upload,
715 options.debug)
David Jamesc0f158a2011-02-22 16:07:29 -0800716
David James8c846492011-01-25 17:07:29 -0800717 if options.sync_host:
David James615e5b52011-06-03 11:10:15 -0700718 uploader._SyncHostPrebuilts(version, options.key, options.git_sync,
719 options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800720
721 if options.board:
David James615e5b52011-06-03 11:10:15 -0700722 uploader._SyncBoardPrebuilts(options.board, version,
David Jamesc0f158a2011-02-22 16:07:29 -0800723 options.key, options.git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700724 options.sync_binhost_conf,
725 options.upload_board_tarball)
David James8c846492011-01-25 17:07:29 -0800726
727if __name__ == '__main__':
728 main()