blob: d50c0dada3dd91d583f82d5f66e257fca3648c7a [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
Brian Harringaf019fb2012-05-10 15:06:13 -07006"""This script is used to upload host prebuilts as well as board BINHOSTS.
David James8c846492011-01-25 17:07:29 -08007
8If the URL starts with 'gs://', we upload using gsutil to Google Storage.
9Otherwise, rsync is used.
10
11After a build is successfully uploaded a file is updated with the proper
12BINHOST version as well as the target board. This file is defined in GIT_FILE
13
14
15To read more about prebuilts/binhost binary packages please refer to:
16http://sites/chromeos/for-team-members/engineering/releng/prebuilt-binaries-for-streamlining-the-build-process
17
18
19Example of uploading prebuilt amd64 host files to Google Storage:
20./prebuilt.py -p /b/cbuild/build -s -u gs://chromeos-prebuilt
21
22Example of uploading x86-dogfood binhosts to Google Storage:
23./prebuilt.py -b x86-dogfood -p /b/cbuild/build/ -u gs://chromeos-prebuilt -g
24
25Example of uploading prebuilt amd64 host files using rsync:
26./prebuilt.py -p /b/cbuild/build -s -u codf30.jail:/tmp
27"""
28
Chris Sosa1dc96132012-05-11 15:40:50 -070029import datetime
30import multiprocessing
31import optparse
32import os
33import sys
34import tempfile
35
36if __name__ == '__main__':
37 import constants
38 sys.path.insert(0, constants.SOURCE_ROOT)
39
40from chromite.lib import cros_build_lib
Brian Harringaf019fb2012-05-10 15:06:13 -070041from chromite.lib import osutils
Chris Sosa1dc96132012-05-11 15:40:50 -070042from chromite.lib.binpkg import (GrabLocalPackageIndex, GrabRemotePackageIndex)
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 James4058b0d2011-12-08 21:24:50 -080049_HOST_ARCH = 'amd64'
David James8c846492011-01-25 17:07:29 -080050_BOARD_PATH = 'chroot/build/%(board)s'
David James4058b0d2011-12-08 21:24:50 -080051_REL_BOARD_PATH = 'board/%(target)s/%(version)s'
52_REL_HOST_PATH = 'host/%(host_arch)s/%(target)s/%(version)s'
David James8c846492011-01-25 17:07:29 -080053# Private overlays to look at for builds to filter
54# relative to build path
55_PRIVATE_OVERLAY_DIR = 'src/private-overlays'
Scott Zawalskiab1bed32011-03-16 15:24:24 -070056_GOOGLESTORAGE_ACL_FILE = 'googlestorage_acl.xml'
David Jamesce619292011-11-08 11:42:36 -080057_BINHOST_BASE_URL = 'https://commondatastorage.googleapis.com/chromeos-prebuilt'
David James8c846492011-01-25 17:07:29 -080058_PREBUILT_BASE_DIR = 'src/third_party/chromiumos-overlay/chromeos/config/'
59# Created in the event of new host targets becoming available
60_PREBUILT_MAKE_CONF = {'amd64': os.path.join(_PREBUILT_BASE_DIR,
61 'make.conf.amd64-host')}
62_BINHOST_CONF_DIR = 'src/third_party/chromiumos-overlay/chromeos/binhost'
63
64
David James8c846492011-01-25 17:07:29 -080065class UploadFailed(Exception):
66 """Raised when one of the files uploaded failed."""
67 pass
68
69class UnknownBoardFormat(Exception):
70 """Raised when a function finds an unknown board format."""
71 pass
72
David James8c846492011-01-25 17:07:29 -080073
David James4058b0d2011-12-08 21:24:50 -080074class BuildTarget(object):
75 """A board/variant/profile tuple."""
76
77 def __init__(self, board_variant, profile=None):
78 self.board_variant = board_variant
79 self.board, _, self.variant = board_variant.partition('_')
80 self.profile = profile
81
82 def __str__(self):
83 if self.profile:
84 return '%s_%s' % (self.board_variant, self.profile)
85 else:
86 return self.board_variant
87
88 def __eq__(self, other):
89 return str(other) == str(self)
90
91 def __hash__(self):
92 return hash(str(self))
93
94
David James8c846492011-01-25 17:07:29 -080095def UpdateLocalFile(filename, value, key='PORTAGE_BINHOST'):
96 """Update the key in file with the value passed.
97 File format:
98 key="value"
99 Note quotes are added automatically
100
101 Args:
102 filename: Name of file to modify.
103 value: Value to write with the key.
104 key: The variable key to update. (Default: PORTAGE_BINHOST)
105 """
106 if os.path.exists(filename):
107 file_fh = open(filename)
108 else:
109 file_fh = open(filename, 'w+')
110 file_lines = []
111 found = False
112 keyval_str = '%(key)s=%(value)s'
113 for line in file_fh:
114 # Strip newlines from end of line. We already add newlines below.
115 line = line.rstrip("\n")
116
117 if len(line.split('=')) != 2:
118 # Skip any line that doesn't fit key=val.
119 file_lines.append(line)
120 continue
121
122 file_var, file_val = line.split('=')
123 if file_var == key:
124 found = True
David James20b2b6f2011-11-18 15:11:58 -0800125 print 'Updating %s=%s to %s="%s"' % (file_var, file_val, key, value)
David James8c846492011-01-25 17:07:29 -0800126 value = '"%s"' % value
127 file_lines.append(keyval_str % {'key': key, 'value': value})
128 else:
129 file_lines.append(keyval_str % {'key': file_var, 'value': file_val})
130
131 if not found:
132 file_lines.append(keyval_str % {'key': key, 'value': value})
133
134 file_fh.close()
135 # write out new file
Brian Harringaf019fb2012-05-10 15:06:13 -0700136 osutils.WriteFile(filename, '\n'.join(file_lines) + '\n')
David James8c846492011-01-25 17:07:29 -0800137
138
David James27fa7d12011-06-29 17:24:14 -0700139def RevGitFile(filename, value, retries=5, key='PORTAGE_BINHOST', dryrun=False):
David James8c846492011-01-25 17:07:29 -0800140 """Update and push the git file.
141
142 Args:
143 filename: file to modify that is in a git repo already
144 value: string representing the version of the prebuilt that has been
145 uploaded.
146 retries: The number of times to retry before giving up, default: 5
147 key: The variable key to update in the git file.
148 (Default: PORTAGE_BINHOST)
149 """
150 prebuilt_branch = 'prebuilt_branch'
David James1b6e67a2011-05-19 21:32:38 -0700151 cwd = os.path.abspath(os.path.dirname(filename))
152 commit = cros_build_lib.RunCommand(['git', 'rev-parse', 'HEAD'], cwd=cwd,
Peter Mayofe0e6872011-04-20 00:52:08 -0400153 redirect_stdout=True).output.rstrip()
David James8c846492011-01-25 17:07:29 -0800154 description = 'Update %s="%s" in %s' % (key, value, filename)
155 print description
David James66009462012-03-25 10:08:38 -0700156
David James8c846492011-01-25 17:07:29 -0800157 try:
David James66009462012-03-25 10:08:38 -0700158 cros_build_lib.CreatePushBranch(prebuilt_branch, cwd)
David James8c846492011-01-25 17:07:29 -0800159 UpdateLocalFile(filename, value, key)
David James1b6e67a2011-05-19 21:32:38 -0700160 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
161 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James66009462012-03-25 10:08:38 -0700162 cros_build_lib.GitPushWithRetry(prebuilt_branch, cwd=cwd, dryrun=dryrun,
163 retries=retries)
David James8c846492011-01-25 17:07:29 -0800164 finally:
David James1b6e67a2011-05-19 21:32:38 -0700165 cros_build_lib.RunCommand(['git', 'checkout', commit], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800166
167
168def GetVersion():
169 """Get the version to put in LATEST and update the git version with."""
170 return datetime.datetime.now().strftime('%d.%m.%y.%H%M%S')
171
172
Brian Harringc274d692012-05-12 00:24:26 -0700173def _RetryRun(cmd, print_cmd=True, cwd=None):
174 """Run the specified command, retrying if necessary.
175
176 Args:
177 cmd: The command to run.
178 print_cmd: Whether to print out the cmd.
179 shell: Whether to treat the command as a shell.
180 cwd: Working directory to run command in.
181
182 Returns:
183 True if the command succeeded. Otherwise, returns False.
184 """
185
186 # TODO(scottz): port to use _Run or similar when it is available in
187 # cros_build_lib.
188 for unused_attempt in range(_RETRIES):
189 try:
190 cros_build_lib.RunCommand(cmd, print_cmd=print_cmd, cwd=cwd)
191 return True
192 except cros_build_lib.RunCommandError:
193 print 'Failed to run %r' % cmd
194 else:
195 print 'Retry failed run %r, giving up' % cmd
196 return False
197
198
David James8c846492011-01-25 17:07:29 -0800199def _GsUpload(args):
200 """Upload to GS bucket.
201
202 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800203 args: a tuple of three arguments that contains local_file, remote_file, and
204 the acl used for uploading the file.
David James8c846492011-01-25 17:07:29 -0800205
206 Returns:
207 Return the arg tuple of two if the upload failed
208 """
David Jamesfd0b0852011-02-23 11:15:36 -0800209 (local_file, remote_file, acl) = args
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700210 CANNED_ACLS = ['public-read', 'private', 'bucket-owner-read',
211 'authenticated-read', 'bucket-owner-full-control',
212 'public-read-write']
213 acl_cmd = None
214 if acl in CANNED_ACLS:
Peter Mayo193f68f2011-04-19 19:08:21 -0400215 cmd = [_GSUTIL_BIN, 'cp', '-a', acl, local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700216 else:
217 # For private uploads we assume that the overlay board is set up properly
218 # and a googlestore_acl.xml is present, if not this script errors
Peter Mayo193f68f2011-04-19 19:08:21 -0400219 cmd = [_GSUTIL_BIN, 'cp', '-a', 'private', local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700220 if not os.path.exists(acl):
221 print >> sys.stderr, ('You are specifying either a file that does not '
222 'exist or an unknown canned acl: %s. Aborting '
223 'upload') % acl
224 # emulate the failing of an upload since we are not uploading the file
225 return (local_file, remote_file)
David James8c846492011-01-25 17:07:29 -0800226
Peter Mayo193f68f2011-04-19 19:08:21 -0400227 acl_cmd = [_GSUTIL_BIN, 'setacl', acl, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700228
Brian Harringc274d692012-05-12 00:24:26 -0700229 if not _RetryRun(cmd, print_cmd=False):
David James8c846492011-01-25 17:07:29 -0800230 return (local_file, remote_file)
231
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700232 if acl_cmd:
233 # Apply the passed in ACL xml file to the uploaded object.
Brian Harringc274d692012-05-12 00:24:26 -0700234 _RetryRun(acl_cmd, print_cmd=False)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700235
236
David Jamesfd0b0852011-02-23 11:15:36 -0800237def RemoteUpload(acl, files, pool=10):
David James8c846492011-01-25 17:07:29 -0800238 """Upload to google storage.
239
240 Create a pool of process and call _GsUpload with the proper arguments.
241
242 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800243 acl: The canned acl used for uploading. acl can be one of: "public-read",
244 "public-read-write", "authenticated-read", "bucket-owner-read",
245 "bucket-owner-full-control", or "private".
David James8c846492011-01-25 17:07:29 -0800246 files: dictionary with keys to local files and values to remote path.
247 pool: integer of maximum proesses to have at the same time.
248
249 Returns:
250 Return a set of tuple arguments of the failed uploads
251 """
252 # TODO(scottz) port this to use _RunManyParallel when it is available in
253 # cros_build_lib
254 pool = multiprocessing.Pool(processes=pool)
255 workers = []
256 for local_file, remote_path in files.iteritems():
David Jamesfd0b0852011-02-23 11:15:36 -0800257 workers.append((local_file, remote_path, acl))
David James8c846492011-01-25 17:07:29 -0800258
259 result = pool.map_async(_GsUpload, workers, chunksize=1)
260 while True:
261 try:
Chris Sosa471532a2011-02-01 15:10:06 -0800262 return set(result.get(60 * 60))
David James8c846492011-01-25 17:07:29 -0800263 except multiprocessing.TimeoutError:
264 pass
265
266
267def GenerateUploadDict(base_local_path, base_remote_path, pkgs):
268 """Build a dictionary of local remote file key pairs to upload.
269
270 Args:
271 base_local_path: The base path to the files on the local hard drive.
272 remote_path: The base path to the remote paths.
273 pkgs: The packages to upload.
274
275 Returns:
276 Returns a dictionary of local_path/remote_path pairs
277 """
278 upload_files = {}
279 for pkg in pkgs:
280 suffix = pkg['CPV'] + '.tbz2'
281 local_path = os.path.join(base_local_path, suffix)
282 assert os.path.exists(local_path)
283 remote_path = '%s/%s' % (base_remote_path.rstrip('/'), suffix)
284 upload_files[local_path] = remote_path
285
286 return upload_files
287
288def GetBoardPathFromCrosOverlayList(build_path, target):
289 """Use the cros_overlay_list to determine the path to the board overlay
290 Args:
291 build_path: The path to the root of the build directory
292 target: The target that we are looking for, could consist of board and
293 board_variant, we handle that properly
294 Returns:
295 The last line from cros_overlay_list as a string
296 """
Chris Sosa471532a2011-02-01 15:10:06 -0800297 script_dir = os.path.join(build_path, 'src/platform/dev/host')
David James4058b0d2011-12-08 21:24:50 -0800298 cmd = ['./cros_overlay_list', '--board', target.board]
299 if target.variant:
300 cmd += ['--variant', target.variant]
David James8c846492011-01-25 17:07:29 -0800301
302 cmd_output = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
303 cwd=script_dir)
304 # We only care about the last entry
305 return cmd_output.output.splitlines().pop()
306
307
308def DeterminePrebuiltConfFile(build_path, target):
309 """Determine the prebuilt.conf file that needs to be updated for prebuilts.
310
311 Args:
312 build_path: The path to the root of the build directory
313 target: String representation of the board. This includes host and board
314 targets
315
316 Returns
317 A string path to a prebuilt.conf file to be updated.
318 """
David James4058b0d2011-12-08 21:24:50 -0800319 if _HOST_ARCH == target:
David James8c846492011-01-25 17:07:29 -0800320 # We are host.
321 # Without more examples of hosts this is a kludge for now.
322 # TODO(Scottz): as new host targets come online expand this to
323 # work more like boards.
Chris Sosa471532a2011-02-01 15:10:06 -0800324 make_path = _PREBUILT_MAKE_CONF[target]
David James8c846492011-01-25 17:07:29 -0800325 else:
326 # We are a board
327 board = GetBoardPathFromCrosOverlayList(build_path, target)
328 make_path = os.path.join(board, 'prebuilt.conf')
329
330 return make_path
331
332
333def UpdateBinhostConfFile(path, key, value):
334 """Update binhost config file file with key=value.
335
336 Args:
337 path: Filename to update.
338 key: Key to update.
339 value: New value for key.
340 """
341 cwd = os.path.dirname(os.path.abspath(path))
342 filename = os.path.basename(path)
Brian Harringaf019fb2012-05-10 15:06:13 -0700343 osutils.SafeMakedirs(cwd)
Brian Harring22edb442012-05-11 23:55:18 -0700344 osutils.WriteFile(path, '', mode='a')
David James8c846492011-01-25 17:07:29 -0800345 UpdateLocalFile(path, value, key)
Chris Sosac13bba52011-05-24 15:14:09 -0700346 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
David James20b2b6f2011-11-18 15:11:58 -0800347 description = 'Update %s="%s" in %s' % (key, value, filename)
Peter Mayo193f68f2011-04-19 19:08:21 -0400348 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800349
350
David Jamesce093af2011-02-23 15:21:58 -0800351def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800352 """Grab all of the packages files associated with a list of binhost_urls.
353
David James05bcb2b2011-02-09 09:25:47 -0800354 Args:
355 binhost_urls: The URLs for the directories containing the Packages files we
356 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800357
358 Returns:
359 A list of PackageIndex objects.
360 """
361 pkg_indexes = []
362 for url in binhost_urls:
363 pkg_index = GrabRemotePackageIndex(url)
364 if pkg_index:
365 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800366 return pkg_indexes
367
368
David James05bcb2b2011-02-09 09:25:47 -0800369
David Jamesc0f158a2011-02-22 16:07:29 -0800370class PrebuiltUploader(object):
371 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800372
David James615e5b52011-06-03 11:10:15 -0700373 def __init__(self, upload_location, acl, binhost_base_url,
David James32b0b2f2011-07-13 20:56:50 -0700374 pkg_indexes, build_path, packages, skip_upload,
David James4058b0d2011-12-08 21:24:50 -0800375 binhost_conf_dir, debug, target, slave_targets):
David Jamesc0f158a2011-02-22 16:07:29 -0800376 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800377
David Jamesc0f158a2011-02-22 16:07:29 -0800378 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800379
David Jamesc0f158a2011-02-22 16:07:29 -0800380 Args:
381 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800382 acl: The canned acl used for uploading to Google Storage. acl can be one
383 of: "public-read", "public-read-write", "authenticated-read",
384 "bucket-owner-read", "bucket-owner-full-control", or "private". If
385 we are not uploading to Google Storage, this parameter is unused.
386 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800387 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
388 uploading duplicate files, we just link to the old files.
David James615e5b52011-06-03 11:10:15 -0700389 build_path: The path to the directory containing the chroot.
390 packages: Packages to upload.
David James32b0b2f2011-07-13 20:56:50 -0700391 skip_upload: Don't actually upload the tarballs.
392 binhost_conf_dir: Directory where to store binhost.conf files.
393 debug: Don't push or upload prebuilts.
David James4058b0d2011-12-08 21:24:50 -0800394 target: BuildTarget managed by this builder.
395 slave_targets: List of BuildTargets managed by slave builders.
David Jamesc0f158a2011-02-22 16:07:29 -0800396 """
397 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800398 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800399 self._binhost_base_url = binhost_base_url
400 self._pkg_indexes = pkg_indexes
David James615e5b52011-06-03 11:10:15 -0700401 self._build_path = build_path
402 self._packages = set(packages)
David James8ece7ee2011-06-29 16:02:30 -0700403 self._skip_upload = skip_upload
David James32b0b2f2011-07-13 20:56:50 -0700404 self._binhost_conf_dir = binhost_conf_dir
David James27fa7d12011-06-29 17:24:14 -0700405 self._debug = debug
David James4058b0d2011-12-08 21:24:50 -0800406 self._target = target
407 self._slave_targets = slave_targets
David James615e5b52011-06-03 11:10:15 -0700408
409 def _ShouldFilterPackage(self, pkg):
410 if not self._packages:
411 return False
412 pym_path = os.path.abspath(os.path.join(self._build_path, _PYM_PATH))
David James710b7dc2012-02-07 16:49:59 -0800413 sys.path.insert(0, pym_path)
David James615e5b52011-06-03 11:10:15 -0700414 import portage.versions
415 cat, pkgname = portage.versions.catpkgsplit(pkg['CPV'])[0:2]
416 cp = '%s/%s' % (cat, pkgname)
417 return pkgname not in self._packages and cp not in self._packages
David James8c846492011-01-25 17:07:29 -0800418
David Jamesc0f158a2011-02-22 16:07:29 -0800419 def _UploadPrebuilt(self, package_path, url_suffix):
420 """Upload host or board prebuilt files to Google Storage space.
David James8c846492011-01-25 17:07:29 -0800421
David Jamesc0f158a2011-02-22 16:07:29 -0800422 Args:
423 package_path: The path to the packages dir.
David Jamesce093af2011-02-23 15:21:58 -0800424 url_suffix: The remote subdirectory where we should upload the packages.
David Jamesa3bba142011-05-26 21:24:20 -0700425
David Jamesc0f158a2011-02-22 16:07:29 -0800426 """
David James8c846492011-01-25 17:07:29 -0800427
David Jamesc0f158a2011-02-22 16:07:29 -0800428 # Process Packages file, removing duplicates and filtered packages.
429 pkg_index = GrabLocalPackageIndex(package_path)
430 pkg_index.SetUploadLocation(self._binhost_base_url, url_suffix)
David James615e5b52011-06-03 11:10:15 -0700431 pkg_index.RemoveFilteredPackages(self._ShouldFilterPackage)
David Jamesc0f158a2011-02-22 16:07:29 -0800432 uploads = pkg_index.ResolveDuplicateUploads(self._pkg_indexes)
David James05bcb2b2011-02-09 09:25:47 -0800433
David Jamesc0f158a2011-02-22 16:07:29 -0800434 # Write Packages file.
435 tmp_packages_file = pkg_index.WriteToNamedTemporaryFile()
David James05bcb2b2011-02-09 09:25:47 -0800436
David Jamesc0f158a2011-02-22 16:07:29 -0800437 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
438 if remote_location.startswith('gs://'):
439 # Build list of files to upload.
440 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
441 remote_file = '%s/Packages' % remote_location.rstrip('/')
442 upload_files[tmp_packages_file.name] = remote_file
David James05bcb2b2011-02-09 09:25:47 -0800443
David Jamesfd0b0852011-02-23 11:15:36 -0800444 failed_uploads = RemoteUpload(self._acl, upload_files)
David Jamesc0f158a2011-02-22 16:07:29 -0800445 if len(failed_uploads) > 1 or (None not in failed_uploads):
David Jamesf4db1122011-03-17 16:18:05 -0700446 error_msg = ['%s -> %s\n' % args for args in failed_uploads if args]
David Jamesc0f158a2011-02-22 16:07:29 -0800447 raise UploadFailed('Error uploading:\n%s' % error_msg)
448 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400449 pkgs = [p['CPV'] + '.tbz2' for p in uploads]
David Jamesc0f158a2011-02-22 16:07:29 -0800450 ssh_server, remote_path = remote_location.split(':', 1)
Peter Mayo193f68f2011-04-19 19:08:21 -0400451 remote_path = remote_path.rstrip('/')
452 pkg_index = tmp_packages_file.name
453 remote_location = remote_location.rstrip('/')
454 remote_packages = '%s/Packages' % remote_location
455 cmds = [['ssh', ssh_server, 'mkdir', '-p', remote_path],
456 ['rsync', '-av', '--chmod=a+r', pkg_index, remote_packages]]
David Jamesc0f158a2011-02-22 16:07:29 -0800457 if pkgs:
Peter Mayo193f68f2011-04-19 19:08:21 -0400458 cmds.append(['rsync', '-Rav'] + pkgs + [remote_location + '/'])
David Jamesc0f158a2011-02-22 16:07:29 -0800459 for cmd in cmds:
Brian Harringc274d692012-05-12 00:24:26 -0700460 if not _RetryRun(cmd, cwd=package_path):
461 raise UploadFailed('Could not run %r' % cmd)
David James8c846492011-01-25 17:07:29 -0800462
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200463 def _UploadBoardTarball(self, board_path, url_suffix, version):
David James8fa34ea2011-04-15 13:00:20 -0700464 """Upload a tarball of the board at the specified path to Google Storage.
465
466 Args:
467 board_path: The path to the board dir.
468 url_suffix: The remote subdirectory where we should upload the packages.
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200469 version: The version of the board.
David James8fa34ea2011-04-15 13:00:20 -0700470 """
471 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
472 assert remote_location.startswith('gs://')
473 cwd, boardname = os.path.split(board_path.rstrip(os.path.sep))
474 tmpdir = tempfile.mkdtemp()
475 try:
476 tarfile = os.path.join(tmpdir, '%s.tbz2' % boardname)
Brian Harringd223a242012-02-03 20:12:10 -0800477 cmd = ['tar', '-I', 'pbzip2', '-cf', tarfile]
David James8fa34ea2011-04-15 13:00:20 -0700478 excluded_paths = ('usr/lib/debug', 'usr/local/autotest', 'packages',
479 'tmp')
480 for path in excluded_paths:
Zdenek Behane3ed3462011-06-16 00:36:08 +0200481 cmd.append('--exclude=%s/*' % path)
482 cmd.append('.')
Brian Harringd223a242012-02-03 20:12:10 -0800483 cros_build_lib.SudoRunCommand(cmd, cwd=os.path.join(cwd, boardname))
David James8fa34ea2011-04-15 13:00:20 -0700484 remote_tarfile = '%s/%s.tbz2' % (remote_location.rstrip('/'), boardname)
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200485 # FIXME(zbehan): Temporary hack to upload amd64-host chroots to a
486 # different gs bucket. The right way is to do the upload in a separate
487 # pass of this script.
488 if boardname == 'amd64-host':
Zdenek Behanaf3c9002011-06-24 10:07:40 +0200489 # FIXME(zbehan): Why does version contain the prefix "chroot-"?
490 remote_tarfile = \
491 'gs://chromiumos-sdk/cros-sdk-%s.tbz2' % version.strip('chroot-')
David James8fa34ea2011-04-15 13:00:20 -0700492 if _GsUpload((tarfile, remote_tarfile, self._acl)):
493 sys.exit(1)
494 finally:
Brian Harringd223a242012-02-03 20:12:10 -0800495 cros_build_lib.SudoRunCommand(['rm', '-rf', tmpdir], cwd=cwd)
David James8fa34ea2011-04-15 13:00:20 -0700496
Brian Harringc274d692012-05-12 00:24:26 -0700497 def _SyncHostPrebuilts(self, version, key, git_sync, sync_binhost_conf):
David Jamesc0f158a2011-02-22 16:07:29 -0800498 """Synchronize host prebuilt files.
David James05bcb2b2011-02-09 09:25:47 -0800499
David Jamesc0f158a2011-02-22 16:07:29 -0800500 This function will sync both the standard host packages, plus the host
501 packages associated with all targets that have been "setup" with the
502 current host's chroot. For instance, if this host has been used to build
503 x86-generic, it will sync the host packages associated with
504 'i686-pc-linux-gnu'. If this host has also been used to build arm-generic,
505 it will also sync the host packages associated with
506 'armv7a-cros-linux-gnueabi'.
David James05bcb2b2011-02-09 09:25:47 -0800507
David Jamesc0f158a2011-02-22 16:07:29 -0800508 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800509 version: A unique string, intended to be included in the upload path,
510 which identifies the version number of the uploaded prebuilts.
511 key: The variable key to update in the git file.
512 git_sync: If set, update make.conf of target to reference the latest
513 prebuilt packages generated here.
514 sync_binhost_conf: If set, update binhost config file in
515 chromiumos-overlay for the host.
516 """
David Jamese2488642011-11-14 16:15:20 -0800517 # Slave boards are listed before the master board so that the master board
518 # takes priority (i.e. x86-generic preflight host prebuilts takes priority
519 # over preflight host prebuilts from other builders.)
520 binhost_urls = []
Brian Harringc274d692012-05-12 00:24:26 -0700521 for target in self._slave_targets + [self._target]:
David James4058b0d2011-12-08 21:24:50 -0800522 url_suffix = _REL_HOST_PATH % {'version': version,
523 'host_arch': _HOST_ARCH,
524 'target': target}
David Jamese2488642011-11-14 16:15:20 -0800525 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James05bcb2b2011-02-09 09:25:47 -0800526
David James4058b0d2011-12-08 21:24:50 -0800527 if self._target == target and not self._skip_upload and not self._debug:
David Jamese2488642011-11-14 16:15:20 -0800528 # Upload prebuilts.
529 package_path = os.path.join(self._build_path, _HOST_PACKAGES_PATH)
530 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8ece7ee2011-06-29 16:02:30 -0700531
David Jamese2488642011-11-14 16:15:20 -0800532 # Record URL where prebuilts were uploaded.
533 binhost_urls.append('%s/%s/' % (self._binhost_base_url.rstrip('/'),
534 packages_url_suffix.rstrip('/')))
535
David James20b2b6f2011-11-18 15:11:58 -0800536 binhost = ' '.join(binhost_urls)
David James8ece7ee2011-06-29 16:02:30 -0700537 if git_sync:
538 git_file = os.path.join(self._build_path,
David James4058b0d2011-12-08 21:24:50 -0800539 _PREBUILT_MAKE_CONF[_HOST_ARCH])
David Jamese2488642011-11-14 16:15:20 -0800540 RevGitFile(git_file, binhost, key=key, dryrun=self._debug)
David James8ece7ee2011-06-29 16:02:30 -0700541 if sync_binhost_conf:
David James32b0b2f2011-07-13 20:56:50 -0700542 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James4058b0d2011-12-08 21:24:50 -0800543 'host', '%s-%s.conf' % (_HOST_ARCH, key))
David Jamese2488642011-11-14 16:15:20 -0800544 UpdateBinhostConfFile(binhost_conf, key, binhost)
David Jamesc0f158a2011-02-22 16:07:29 -0800545
Brian Harringc274d692012-05-12 00:24:26 -0700546 def _SyncBoardPrebuilts(self, version, key, git_sync, sync_binhost_conf,
547 upload_board_tarball):
David Jamesc0f158a2011-02-22 16:07:29 -0800548 """Synchronize board prebuilt files.
549
550 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800551 version: A unique string, intended to be included in the upload path,
552 which identifies the version number of the uploaded prebuilts.
553 key: The variable key to update in the git file.
554 git_sync: If set, update make.conf of target to reference the latest
555 prebuilt packages generated here.
556 sync_binhost_conf: If set, update binhost config file in
557 chromiumos-overlay for the current board.
David James8fa34ea2011-04-15 13:00:20 -0700558 upload_board_tarball: Include a tarball of the board in our upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800559 """
Brian Harringc274d692012-05-12 00:24:26 -0700560 for target in self._slave_targets + [self._target]:
David Jamese2488642011-11-14 16:15:20 -0800561 board_path = os.path.join(self._build_path,
David James4058b0d2011-12-08 21:24:50 -0800562 _BOARD_PATH % {'board': target.board_variant})
David Jamese2488642011-11-14 16:15:20 -0800563 package_path = os.path.join(board_path, 'packages')
David James4058b0d2011-12-08 21:24:50 -0800564 url_suffix = _REL_BOARD_PATH % {'target': target, 'version': version}
David Jamese2488642011-11-14 16:15:20 -0800565 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James8fa34ea2011-04-15 13:00:20 -0700566
David James4058b0d2011-12-08 21:24:50 -0800567 if self._target == target and not self._skip_upload and not self._debug:
David Jamese2488642011-11-14 16:15:20 -0800568 # Upload board tarballs in the background.
569 if upload_board_tarball:
570 tar_process = multiprocessing.Process(target=self._UploadBoardTarball,
571 args=(board_path, url_suffix,
572 version))
573 tar_process.start()
David James8fa34ea2011-04-15 13:00:20 -0700574
David Jamese2488642011-11-14 16:15:20 -0800575 # Upload prebuilts.
576 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8fa34ea2011-04-15 13:00:20 -0700577
David Jamese2488642011-11-14 16:15:20 -0800578 # Make sure we finished uploading the board tarballs.
579 if upload_board_tarball:
580 tar_process.join()
581 assert tar_process.exitcode == 0
582 # TODO(zbehan): This should be done cleaner.
David James4058b0d2011-12-08 21:24:50 -0800583 if target.board == 'amd64-host':
David Jamese2488642011-11-14 16:15:20 -0800584 sdk_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James8ece7ee2011-06-29 16:02:30 -0700585 'host/sdk_version.conf')
David Jamese2488642011-11-14 16:15:20 -0800586 RevGitFile(sdk_conf, version.strip('chroot-'),
587 key='SDK_LATEST_VERSION', dryrun=self._debug)
David Jamesc0f158a2011-02-22 16:07:29 -0800588
David Jamese2488642011-11-14 16:15:20 -0800589 # Record URL where prebuilts were uploaded.
590 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
591 packages_url_suffix.rstrip('/'))
592
593 if git_sync:
David James4058b0d2011-12-08 21:24:50 -0800594 git_file = DeterminePrebuiltConfFile(self._build_path, target)
David Jamese2488642011-11-14 16:15:20 -0800595 RevGitFile(git_file, url_value, key=key, dryrun=self._debug)
596 if sync_binhost_conf:
597 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James4058b0d2011-12-08 21:24:50 -0800598 'target', '%s-%s.conf' % (target, key))
David Jamese2488642011-11-14 16:15:20 -0800599 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800600
601
Brian Harringc274d692012-05-12 00:24:26 -0700602def usage(parser, msg):
David James8c846492011-01-25 17:07:29 -0800603 """Display usage message and parser help then exit with 1."""
604 print >> sys.stderr, msg
605 parser.print_help()
606 sys.exit(1)
607
David James4058b0d2011-12-08 21:24:50 -0800608
Brian Harringc274d692012-05-12 00:24:26 -0700609def add_slave_board(_option, _opt_str, value, parser):
David James4058b0d2011-12-08 21:24:50 -0800610 parser.values.slave_targets.append(BuildTarget(value))
611
612
Brian Harringc274d692012-05-12 00:24:26 -0700613def add_slave_profile(_option, _opt_str, value, parser):
David James4058b0d2011-12-08 21:24:50 -0800614 if not parser.values.slave_targets:
Brian Harringc274d692012-05-12 00:24:26 -0700615 usage(parser, 'Must specify --slave-board before --slave-profile')
David James4058b0d2011-12-08 21:24:50 -0800616 if parser.values.slave_targets[-1].profile is not None:
Brian Harringc274d692012-05-12 00:24:26 -0700617 usage(parser, 'Cannot specify --slave-profile twice for same board')
David James4058b0d2011-12-08 21:24:50 -0800618 parser.values.slave_targets[-1].profile = value
619
620
David Jamesc0f158a2011-02-22 16:07:29 -0800621def ParseOptions():
David James8c846492011-01-25 17:07:29 -0800622 parser = optparse.OptionParser()
623 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
624 default=_BINHOST_BASE_URL,
625 help='Base URL to use for binhost in make.conf updates')
626 parser.add_option('', '--previous-binhost-url', action='append',
627 default=[], dest='previous_binhost_url',
628 help='Previous binhost URL')
629 parser.add_option('-b', '--board', dest='board', default=None,
630 help='Board type that was built on this machine')
David James4058b0d2011-12-08 21:24:50 -0800631 parser.add_option('', '--profile', dest='profile', default=None,
632 help='Profile that was built on this machine')
633 parser.add_option('', '--slave-board', default=[], action='callback',
634 dest='slave_targets', type='string',
Brian Harringc274d692012-05-12 00:24:26 -0700635 callback=add_slave_board,
David James4058b0d2011-12-08 21:24:50 -0800636 help='Board type that was built on a slave machine. To '
637 'add a profile to this board, use --slave-profile.')
638 parser.add_option('', '--slave-profile', action='callback', type='string',
Brian Harringc274d692012-05-12 00:24:26 -0700639 callback=add_slave_profile,
David James4058b0d2011-12-08 21:24:50 -0800640 help='Board profile that was built on a slave machine. '
641 'Applies to previous slave board.')
David James8c846492011-01-25 17:07:29 -0800642 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800643 help='Path to the directory containing the chroot')
David James615e5b52011-06-03 11:10:15 -0700644 parser.add_option('', '--packages', action='append',
645 default=[], dest='packages',
646 help='Only include the specified packages. '
647 '(Default is to include all packages.)')
David James8c846492011-01-25 17:07:29 -0800648 parser.add_option('-s', '--sync-host', dest='sync_host',
649 default=False, action='store_true',
650 help='Sync host prebuilts')
651 parser.add_option('-g', '--git-sync', dest='git_sync',
652 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800653 help='Enable git version sync (This commits to a repo.) '
654 'This is used by full builders to commit directly '
655 'to board overlays.')
David James8c846492011-01-25 17:07:29 -0800656 parser.add_option('-u', '--upload', dest='upload',
657 default=None,
658 help='Upload location')
659 parser.add_option('-V', '--prepend-version', dest='prepend_version',
660 default=None,
661 help='Add an identifier to the front of the version')
662 parser.add_option('-f', '--filters', dest='filters', action='store_true',
663 default=False,
664 help='Turn on filtering of private ebuild packages')
665 parser.add_option('-k', '--key', dest='key',
666 default='PORTAGE_BINHOST',
667 help='Key to update in make.conf / binhost.conf')
David James8ece7ee2011-06-29 16:02:30 -0700668 parser.add_option('', '--set-version', dest='set_version',
669 default=None,
670 help='Specify the version string')
David James8c846492011-01-25 17:07:29 -0800671 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
672 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800673 help='Update binhost.conf in chromiumos-overlay or '
674 'chromeos-overlay. Commit the changes, but don\'t '
675 'push them. This is used for preflight binhosts.')
David James32b0b2f2011-07-13 20:56:50 -0700676 parser.add_option('', '--binhost-conf-dir', dest='binhost_conf_dir',
677 default=_BINHOST_CONF_DIR,
678 help='Directory to commit binhost config with '
679 '--sync-binhost-conf.')
David Jamesfd0b0852011-02-23 11:15:36 -0800680 parser.add_option('-P', '--private', dest='private', action='store_true',
681 default=False, help='Mark gs:// uploads as private.')
David James8ece7ee2011-06-29 16:02:30 -0700682 parser.add_option('', '--skip-upload', dest='skip_upload',
683 action='store_true', default=False,
684 help='Skip upload step.')
David James8fa34ea2011-04-15 13:00:20 -0700685 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
686 action='store_true', default=False,
687 help='Upload board tarball to Google Storage.')
David James27fa7d12011-06-29 17:24:14 -0700688 parser.add_option('', '--debug', dest='debug',
689 action='store_true', default=False,
690 help='Don\'t push or upload prebuilts.')
David James8c846492011-01-25 17:07:29 -0800691
692 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800693 if not options.build_path:
Brian Harringc274d692012-05-12 00:24:26 -0700694 usage(parser, 'Error: you need provide a chroot path')
David James8ece7ee2011-06-29 16:02:30 -0700695 if not options.upload and not options.skip_upload:
Brian Harringc274d692012-05-12 00:24:26 -0700696 usage(parser, 'Error: you need to provide an upload location using -u')
David James8ece7ee2011-06-29 16:02:30 -0700697 if not options.set_version and options.skip_upload:
Brian Harringc274d692012-05-12 00:24:26 -0700698 usage(parser, 'Error: If you are using --skip-upload, you must specify a '
David James8ece7ee2011-06-29 16:02:30 -0700699 'version number using --set-version.')
David James9417f272011-05-26 13:24:47 -0700700 if args:
Brian Harringc274d692012-05-12 00:24:26 -0700701 usage(parser, 'Error: invalid arguments passed to prebuilt.py: %r' % args)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700702
Brian Harringc274d692012-05-12 00:24:26 -0700703 options.target = BuildTarget(options.board, options.profile)
704 if options.target in options.slave_targets:
705 usage(parser, 'Error: --board/--profile must not also be a slave target.')
David Jamese2488642011-11-14 16:15:20 -0800706
David James4058b0d2011-12-08 21:24:50 -0800707 if len(set(options.slave_targets)) != len(options.slave_targets):
Brian Harringc274d692012-05-12 00:24:26 -0700708 usage(parser, 'Error: --slave-boards must not have duplicates.')
David Jamese2488642011-11-14 16:15:20 -0800709
David James4058b0d2011-12-08 21:24:50 -0800710 if options.slave_targets and options.git_sync:
Brian Harringc274d692012-05-12 00:24:26 -0700711 usage(parser, 'Error: --slave-boards is not compatible with --git-sync')
David Jamese2488642011-11-14 16:15:20 -0800712
David James8ece7ee2011-06-29 16:02:30 -0700713 if (options.upload_board_tarball and options.skip_upload and
714 options.board == 'amd64-host'):
Brian Harringc274d692012-05-12 00:24:26 -0700715 usage(parser, 'Error: --skip-upload is not compatible with '
David James8ece7ee2011-06-29 16:02:30 -0700716 '--upload-board-tarball and --board=amd64-host')
David James8fa34ea2011-04-15 13:00:20 -0700717
David James8ece7ee2011-06-29 16:02:30 -0700718 if (options.upload_board_tarball and not options.skip_upload and
719 not options.upload.startswith('gs://')):
Brian Harringc274d692012-05-12 00:24:26 -0700720 usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
David James8fa34ea2011-04-15 13:00:20 -0700721 '--upload must be a gs:// URL.')
722
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700723 if options.private:
724 if options.sync_host:
Brian Harringc274d692012-05-12 00:24:26 -0700725 usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700726 'together, we do not support private host prebuilts')
727
David James8ece7ee2011-06-29 16:02:30 -0700728 if not options.upload or not options.upload.startswith('gs://'):
Brian Harringc274d692012-05-12 00:24:26 -0700729 usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700730 '--upload must be a gs:// URL.')
731
732 if options.binhost_base_url != _BINHOST_BASE_URL:
Brian Harringc274d692012-05-12 00:24:26 -0700733 usage(parser, 'Error: when using --private the --binhost-base-url '
David Jamese2488642011-11-14 16:15:20 -0800734 'is automatically derived.')
David James27fa7d12011-06-29 17:24:14 -0700735
Brian Harringc274d692012-05-12 00:24:26 -0700736 return options
David Jamesc0f158a2011-02-22 16:07:29 -0800737
738def main():
David Jamesdb401072011-06-10 12:17:16 -0700739 # Set umask to a sane value so that files created as root are readable.
740 os.umask(022)
741
Brian Harringc274d692012-05-12 00:24:26 -0700742 options = ParseOptions()
David Jamesc0f158a2011-02-22 16:07:29 -0800743
David James05bcb2b2011-02-09 09:25:47 -0800744 # Calculate a list of Packages index files to compare against. Whenever we
745 # upload a package, we check to make sure it's not already stored in one of
746 # the packages files we uploaded. This list of packages files might contain
747 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800748 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800749
David James8ece7ee2011-06-29 16:02:30 -0700750 if options.set_version:
751 version = options.set_version
752 else:
753 version = GetVersion()
David Jamesc0f158a2011-02-22 16:07:29 -0800754 if options.prepend_version:
755 version = '%s-%s' % (options.prepend_version, version)
756
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700757 acl = 'public-read'
758 binhost_base_url = options.binhost_base_url
759
Brian Harringc274d692012-05-12 00:24:26 -0700760 if options.private:
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700761 binhost_base_url = options.upload
762 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
Brian Harringc274d692012-05-12 00:24:26 -0700763 options.target)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700764 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
765
766 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David James615e5b52011-06-03 11:10:15 -0700767 pkg_indexes, options.build_path,
David James27fa7d12011-06-29 17:24:14 -0700768 options.packages, options.skip_upload,
David Jamese2488642011-11-14 16:15:20 -0800769 options.binhost_conf_dir, options.debug,
Brian Harringc274d692012-05-12 00:24:26 -0700770 options.target, options.slave_targets)
David Jamesc0f158a2011-02-22 16:07:29 -0800771
David James8c846492011-01-25 17:07:29 -0800772 if options.sync_host:
Brian Harringc274d692012-05-12 00:24:26 -0700773 uploader._SyncHostPrebuilts(version, options.key, options.git_sync,
774 options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800775
776 if options.board:
Brian Harringc274d692012-05-12 00:24:26 -0700777 uploader._SyncBoardPrebuilts(version, options.key, options.git_sync,
778 options.sync_binhost_conf,
779 options.upload_board_tarball)
David James8c846492011-01-25 17:07:29 -0800780
781if __name__ == '__main__':
782 main()