blob: 6e7a1ece8c221df60fde5ede48bd6830a586f624 [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:
Brian Harring2a014302012-05-12 00:53:33 -0700132 value = '"%s"' % value
David James8c846492011-01-25 17:07:29 -0800133 file_lines.append(keyval_str % {'key': key, 'value': value})
134
135 file_fh.close()
136 # write out new file
Brian Harringaf019fb2012-05-10 15:06:13 -0700137 osutils.WriteFile(filename, '\n'.join(file_lines) + '\n')
David James8c846492011-01-25 17:07:29 -0800138
139
David James27fa7d12011-06-29 17:24:14 -0700140def RevGitFile(filename, value, retries=5, key='PORTAGE_BINHOST', dryrun=False):
David James8c846492011-01-25 17:07:29 -0800141 """Update and push the git file.
142
143 Args:
144 filename: file to modify that is in a git repo already
145 value: string representing the version of the prebuilt that has been
146 uploaded.
147 retries: The number of times to retry before giving up, default: 5
148 key: The variable key to update in the git file.
149 (Default: PORTAGE_BINHOST)
150 """
151 prebuilt_branch = 'prebuilt_branch'
David James1b6e67a2011-05-19 21:32:38 -0700152 cwd = os.path.abspath(os.path.dirname(filename))
153 commit = cros_build_lib.RunCommand(['git', 'rev-parse', 'HEAD'], cwd=cwd,
Peter Mayofe0e6872011-04-20 00:52:08 -0400154 redirect_stdout=True).output.rstrip()
David James8c846492011-01-25 17:07:29 -0800155 description = 'Update %s="%s" in %s' % (key, value, filename)
156 print description
David James66009462012-03-25 10:08:38 -0700157
David James8c846492011-01-25 17:07:29 -0800158 try:
David James66009462012-03-25 10:08:38 -0700159 cros_build_lib.CreatePushBranch(prebuilt_branch, cwd)
David James8c846492011-01-25 17:07:29 -0800160 UpdateLocalFile(filename, value, key)
David James1b6e67a2011-05-19 21:32:38 -0700161 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
162 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James66009462012-03-25 10:08:38 -0700163 cros_build_lib.GitPushWithRetry(prebuilt_branch, cwd=cwd, dryrun=dryrun,
164 retries=retries)
David James8c846492011-01-25 17:07:29 -0800165 finally:
David James1b6e67a2011-05-19 21:32:38 -0700166 cros_build_lib.RunCommand(['git', 'checkout', commit], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800167
168
169def GetVersion():
170 """Get the version to put in LATEST and update the git version with."""
171 return datetime.datetime.now().strftime('%d.%m.%y.%H%M%S')
172
173
Brian Harringc274d692012-05-12 00:24:26 -0700174def _RetryRun(cmd, print_cmd=True, cwd=None):
175 """Run the specified command, retrying if necessary.
176
177 Args:
178 cmd: The command to run.
179 print_cmd: Whether to print out the cmd.
180 shell: Whether to treat the command as a shell.
181 cwd: Working directory to run command in.
182
183 Returns:
184 True if the command succeeded. Otherwise, returns False.
185 """
186
187 # TODO(scottz): port to use _Run or similar when it is available in
188 # cros_build_lib.
189 for unused_attempt in range(_RETRIES):
190 try:
191 cros_build_lib.RunCommand(cmd, print_cmd=print_cmd, cwd=cwd)
192 return True
193 except cros_build_lib.RunCommandError:
194 print 'Failed to run %r' % cmd
195 else:
196 print 'Retry failed run %r, giving up' % cmd
197 return False
198
199
David James8c846492011-01-25 17:07:29 -0800200def _GsUpload(args):
201 """Upload to GS bucket.
202
203 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800204 args: a tuple of three arguments that contains local_file, remote_file, and
205 the acl used for uploading the file.
David James8c846492011-01-25 17:07:29 -0800206
207 Returns:
208 Return the arg tuple of two if the upload failed
209 """
David Jamesfd0b0852011-02-23 11:15:36 -0800210 (local_file, remote_file, acl) = args
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700211 CANNED_ACLS = ['public-read', 'private', 'bucket-owner-read',
212 'authenticated-read', 'bucket-owner-full-control',
213 'public-read-write']
214 acl_cmd = None
215 if acl in CANNED_ACLS:
Peter Mayo193f68f2011-04-19 19:08:21 -0400216 cmd = [_GSUTIL_BIN, 'cp', '-a', acl, local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700217 else:
218 # For private uploads we assume that the overlay board is set up properly
219 # and a googlestore_acl.xml is present, if not this script errors
Peter Mayo193f68f2011-04-19 19:08:21 -0400220 cmd = [_GSUTIL_BIN, 'cp', '-a', 'private', local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700221 if not os.path.exists(acl):
222 print >> sys.stderr, ('You are specifying either a file that does not '
223 'exist or an unknown canned acl: %s. Aborting '
224 'upload') % acl
225 # emulate the failing of an upload since we are not uploading the file
226 return (local_file, remote_file)
David James8c846492011-01-25 17:07:29 -0800227
Peter Mayo193f68f2011-04-19 19:08:21 -0400228 acl_cmd = [_GSUTIL_BIN, 'setacl', acl, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700229
Brian Harringc274d692012-05-12 00:24:26 -0700230 if not _RetryRun(cmd, print_cmd=False):
David James8c846492011-01-25 17:07:29 -0800231 return (local_file, remote_file)
232
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700233 if acl_cmd:
234 # Apply the passed in ACL xml file to the uploaded object.
Brian Harringc274d692012-05-12 00:24:26 -0700235 _RetryRun(acl_cmd, print_cmd=False)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700236
237
David Jamesfd0b0852011-02-23 11:15:36 -0800238def RemoteUpload(acl, files, pool=10):
David James8c846492011-01-25 17:07:29 -0800239 """Upload to google storage.
240
241 Create a pool of process and call _GsUpload with the proper arguments.
242
243 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800244 acl: The canned acl used for uploading. acl can be one of: "public-read",
245 "public-read-write", "authenticated-read", "bucket-owner-read",
246 "bucket-owner-full-control", or "private".
David James8c846492011-01-25 17:07:29 -0800247 files: dictionary with keys to local files and values to remote path.
248 pool: integer of maximum proesses to have at the same time.
249
250 Returns:
251 Return a set of tuple arguments of the failed uploads
252 """
253 # TODO(scottz) port this to use _RunManyParallel when it is available in
254 # cros_build_lib
255 pool = multiprocessing.Pool(processes=pool)
256 workers = []
257 for local_file, remote_path in files.iteritems():
David Jamesfd0b0852011-02-23 11:15:36 -0800258 workers.append((local_file, remote_path, acl))
David James8c846492011-01-25 17:07:29 -0800259
260 result = pool.map_async(_GsUpload, workers, chunksize=1)
261 while True:
262 try:
Chris Sosa471532a2011-02-01 15:10:06 -0800263 return set(result.get(60 * 60))
David James8c846492011-01-25 17:07:29 -0800264 except multiprocessing.TimeoutError:
265 pass
266
267
268def GenerateUploadDict(base_local_path, base_remote_path, pkgs):
269 """Build a dictionary of local remote file key pairs to upload.
270
271 Args:
272 base_local_path: The base path to the files on the local hard drive.
273 remote_path: The base path to the remote paths.
274 pkgs: The packages to upload.
275
276 Returns:
277 Returns a dictionary of local_path/remote_path pairs
278 """
279 upload_files = {}
280 for pkg in pkgs:
281 suffix = pkg['CPV'] + '.tbz2'
282 local_path = os.path.join(base_local_path, suffix)
283 assert os.path.exists(local_path)
284 remote_path = '%s/%s' % (base_remote_path.rstrip('/'), suffix)
285 upload_files[local_path] = remote_path
286
287 return upload_files
288
289def GetBoardPathFromCrosOverlayList(build_path, target):
290 """Use the cros_overlay_list to determine the path to the board overlay
291 Args:
292 build_path: The path to the root of the build directory
293 target: The target that we are looking for, could consist of board and
294 board_variant, we handle that properly
295 Returns:
296 The last line from cros_overlay_list as a string
297 """
Chris Sosa471532a2011-02-01 15:10:06 -0800298 script_dir = os.path.join(build_path, 'src/platform/dev/host')
David James4058b0d2011-12-08 21:24:50 -0800299 cmd = ['./cros_overlay_list', '--board', target.board]
300 if target.variant:
301 cmd += ['--variant', target.variant]
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 """
David James4058b0d2011-12-08 21:24:50 -0800320 if _HOST_ARCH == target:
David James8c846492011-01-25 17:07:29 -0800321 # 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)
Brian Harringaf019fb2012-05-10 15:06:13 -0700344 osutils.SafeMakedirs(cwd)
Brian Harring22edb442012-05-11 23:55:18 -0700345 osutils.WriteFile(path, '', mode='a')
David James8c846492011-01-25 17:07:29 -0800346 UpdateLocalFile(path, value, key)
Chris Sosac13bba52011-05-24 15:14:09 -0700347 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
David James20b2b6f2011-11-18 15:11:58 -0800348 description = 'Update %s="%s" in %s' % (key, value, filename)
Peter Mayo193f68f2011-04-19 19:08:21 -0400349 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800350
351
David Jamesce093af2011-02-23 15:21:58 -0800352def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800353 """Grab all of the packages files associated with a list of binhost_urls.
354
David James05bcb2b2011-02-09 09:25:47 -0800355 Args:
356 binhost_urls: The URLs for the directories containing the Packages files we
357 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800358
359 Returns:
360 A list of PackageIndex objects.
361 """
362 pkg_indexes = []
363 for url in binhost_urls:
364 pkg_index = GrabRemotePackageIndex(url)
365 if pkg_index:
366 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800367 return pkg_indexes
368
369
David James05bcb2b2011-02-09 09:25:47 -0800370
David Jamesc0f158a2011-02-22 16:07:29 -0800371class PrebuiltUploader(object):
372 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800373
David James615e5b52011-06-03 11:10:15 -0700374 def __init__(self, upload_location, acl, binhost_base_url,
David James32b0b2f2011-07-13 20:56:50 -0700375 pkg_indexes, build_path, packages, skip_upload,
David James4058b0d2011-12-08 21:24:50 -0800376 binhost_conf_dir, debug, target, slave_targets):
David Jamesc0f158a2011-02-22 16:07:29 -0800377 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800378
David Jamesc0f158a2011-02-22 16:07:29 -0800379 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800380
David Jamesc0f158a2011-02-22 16:07:29 -0800381 Args:
382 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800383 acl: The canned acl used for uploading to Google Storage. acl can be one
384 of: "public-read", "public-read-write", "authenticated-read",
385 "bucket-owner-read", "bucket-owner-full-control", or "private". If
386 we are not uploading to Google Storage, this parameter is unused.
387 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800388 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
389 uploading duplicate files, we just link to the old files.
David James615e5b52011-06-03 11:10:15 -0700390 build_path: The path to the directory containing the chroot.
391 packages: Packages to upload.
David James32b0b2f2011-07-13 20:56:50 -0700392 skip_upload: Don't actually upload the tarballs.
393 binhost_conf_dir: Directory where to store binhost.conf files.
394 debug: Don't push or upload prebuilts.
David James4058b0d2011-12-08 21:24:50 -0800395 target: BuildTarget managed by this builder.
396 slave_targets: List of BuildTargets managed by slave builders.
David Jamesc0f158a2011-02-22 16:07:29 -0800397 """
398 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800399 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800400 self._binhost_base_url = binhost_base_url
401 self._pkg_indexes = pkg_indexes
David James615e5b52011-06-03 11:10:15 -0700402 self._build_path = build_path
403 self._packages = set(packages)
David James8ece7ee2011-06-29 16:02:30 -0700404 self._skip_upload = skip_upload
David James32b0b2f2011-07-13 20:56:50 -0700405 self._binhost_conf_dir = binhost_conf_dir
David James27fa7d12011-06-29 17:24:14 -0700406 self._debug = debug
David James4058b0d2011-12-08 21:24:50 -0800407 self._target = target
408 self._slave_targets = slave_targets
David James615e5b52011-06-03 11:10:15 -0700409
410 def _ShouldFilterPackage(self, pkg):
411 if not self._packages:
412 return False
413 pym_path = os.path.abspath(os.path.join(self._build_path, _PYM_PATH))
David James710b7dc2012-02-07 16:49:59 -0800414 sys.path.insert(0, pym_path)
David James615e5b52011-06-03 11:10:15 -0700415 import portage.versions
416 cat, pkgname = portage.versions.catpkgsplit(pkg['CPV'])[0:2]
417 cp = '%s/%s' % (cat, pkgname)
418 return pkgname not in self._packages and cp not in self._packages
David James8c846492011-01-25 17:07:29 -0800419
David Jamesc0f158a2011-02-22 16:07:29 -0800420 def _UploadPrebuilt(self, package_path, url_suffix):
421 """Upload host or board prebuilt files to Google Storage space.
David James8c846492011-01-25 17:07:29 -0800422
David Jamesc0f158a2011-02-22 16:07:29 -0800423 Args:
424 package_path: The path to the packages dir.
David Jamesce093af2011-02-23 15:21:58 -0800425 url_suffix: The remote subdirectory where we should upload the packages.
David Jamesa3bba142011-05-26 21:24:20 -0700426
David Jamesc0f158a2011-02-22 16:07:29 -0800427 """
David James8c846492011-01-25 17:07:29 -0800428
David Jamesc0f158a2011-02-22 16:07:29 -0800429 # Process Packages file, removing duplicates and filtered packages.
430 pkg_index = GrabLocalPackageIndex(package_path)
431 pkg_index.SetUploadLocation(self._binhost_base_url, url_suffix)
David James615e5b52011-06-03 11:10:15 -0700432 pkg_index.RemoveFilteredPackages(self._ShouldFilterPackage)
David Jamesc0f158a2011-02-22 16:07:29 -0800433 uploads = pkg_index.ResolveDuplicateUploads(self._pkg_indexes)
David James05bcb2b2011-02-09 09:25:47 -0800434
David Jamesc0f158a2011-02-22 16:07:29 -0800435 # Write Packages file.
436 tmp_packages_file = pkg_index.WriteToNamedTemporaryFile()
David James05bcb2b2011-02-09 09:25:47 -0800437
David Jamesc0f158a2011-02-22 16:07:29 -0800438 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
439 if remote_location.startswith('gs://'):
440 # Build list of files to upload.
441 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
442 remote_file = '%s/Packages' % remote_location.rstrip('/')
443 upload_files[tmp_packages_file.name] = remote_file
David James05bcb2b2011-02-09 09:25:47 -0800444
David Jamesfd0b0852011-02-23 11:15:36 -0800445 failed_uploads = RemoteUpload(self._acl, upload_files)
David Jamesc0f158a2011-02-22 16:07:29 -0800446 if len(failed_uploads) > 1 or (None not in failed_uploads):
David Jamesf4db1122011-03-17 16:18:05 -0700447 error_msg = ['%s -> %s\n' % args for args in failed_uploads if args]
David Jamesc0f158a2011-02-22 16:07:29 -0800448 raise UploadFailed('Error uploading:\n%s' % error_msg)
449 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400450 pkgs = [p['CPV'] + '.tbz2' for p in uploads]
David Jamesc0f158a2011-02-22 16:07:29 -0800451 ssh_server, remote_path = remote_location.split(':', 1)
Peter Mayo193f68f2011-04-19 19:08:21 -0400452 remote_path = remote_path.rstrip('/')
453 pkg_index = tmp_packages_file.name
454 remote_location = remote_location.rstrip('/')
455 remote_packages = '%s/Packages' % remote_location
456 cmds = [['ssh', ssh_server, 'mkdir', '-p', remote_path],
457 ['rsync', '-av', '--chmod=a+r', pkg_index, remote_packages]]
David Jamesc0f158a2011-02-22 16:07:29 -0800458 if pkgs:
Peter Mayo193f68f2011-04-19 19:08:21 -0400459 cmds.append(['rsync', '-Rav'] + pkgs + [remote_location + '/'])
David Jamesc0f158a2011-02-22 16:07:29 -0800460 for cmd in cmds:
Brian Harringc274d692012-05-12 00:24:26 -0700461 if not _RetryRun(cmd, cwd=package_path):
462 raise UploadFailed('Could not run %r' % cmd)
David James8c846492011-01-25 17:07:29 -0800463
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200464 def _UploadBoardTarball(self, board_path, url_suffix, version):
David James8fa34ea2011-04-15 13:00:20 -0700465 """Upload a tarball of the board at the specified path to Google Storage.
466
467 Args:
468 board_path: The path to the board dir.
469 url_suffix: The remote subdirectory where we should upload the packages.
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200470 version: The version of the board.
David James8fa34ea2011-04-15 13:00:20 -0700471 """
472 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
473 assert remote_location.startswith('gs://')
474 cwd, boardname = os.path.split(board_path.rstrip(os.path.sep))
475 tmpdir = tempfile.mkdtemp()
476 try:
477 tarfile = os.path.join(tmpdir, '%s.tbz2' % boardname)
Brian Harringd223a242012-02-03 20:12:10 -0800478 cmd = ['tar', '-I', 'pbzip2', '-cf', tarfile]
David James8fa34ea2011-04-15 13:00:20 -0700479 excluded_paths = ('usr/lib/debug', 'usr/local/autotest', 'packages',
480 'tmp')
481 for path in excluded_paths:
Zdenek Behane3ed3462011-06-16 00:36:08 +0200482 cmd.append('--exclude=%s/*' % path)
483 cmd.append('.')
Brian Harringd223a242012-02-03 20:12:10 -0800484 cros_build_lib.SudoRunCommand(cmd, cwd=os.path.join(cwd, boardname))
David James8fa34ea2011-04-15 13:00:20 -0700485 remote_tarfile = '%s/%s.tbz2' % (remote_location.rstrip('/'), boardname)
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200486 # FIXME(zbehan): Temporary hack to upload amd64-host chroots to a
487 # different gs bucket. The right way is to do the upload in a separate
488 # pass of this script.
489 if boardname == 'amd64-host':
Zdenek Behanaf3c9002011-06-24 10:07:40 +0200490 # FIXME(zbehan): Why does version contain the prefix "chroot-"?
491 remote_tarfile = \
492 'gs://chromiumos-sdk/cros-sdk-%s.tbz2' % version.strip('chroot-')
David James8fa34ea2011-04-15 13:00:20 -0700493 if _GsUpload((tarfile, remote_tarfile, self._acl)):
494 sys.exit(1)
495 finally:
Brian Harringd223a242012-02-03 20:12:10 -0800496 cros_build_lib.SudoRunCommand(['rm', '-rf', tmpdir], cwd=cwd)
David James8fa34ea2011-04-15 13:00:20 -0700497
Brian Harringc274d692012-05-12 00:24:26 -0700498 def _SyncHostPrebuilts(self, version, key, git_sync, sync_binhost_conf):
David Jamesc0f158a2011-02-22 16:07:29 -0800499 """Synchronize host prebuilt files.
David James05bcb2b2011-02-09 09:25:47 -0800500
David Jamesc0f158a2011-02-22 16:07:29 -0800501 This function will sync both the standard host packages, plus the host
502 packages associated with all targets that have been "setup" with the
503 current host's chroot. For instance, if this host has been used to build
504 x86-generic, it will sync the host packages associated with
505 'i686-pc-linux-gnu'. If this host has also been used to build arm-generic,
506 it will also sync the host packages associated with
507 'armv7a-cros-linux-gnueabi'.
David James05bcb2b2011-02-09 09:25:47 -0800508
David Jamesc0f158a2011-02-22 16:07:29 -0800509 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800510 version: A unique string, intended to be included in the upload path,
511 which identifies the version number of the uploaded prebuilts.
512 key: The variable key to update in the git file.
513 git_sync: If set, update make.conf of target to reference the latest
514 prebuilt packages generated here.
515 sync_binhost_conf: If set, update binhost config file in
516 chromiumos-overlay for the host.
517 """
David Jamese2488642011-11-14 16:15:20 -0800518 # Slave boards are listed before the master board so that the master board
519 # takes priority (i.e. x86-generic preflight host prebuilts takes priority
520 # over preflight host prebuilts from other builders.)
521 binhost_urls = []
Brian Harringc274d692012-05-12 00:24:26 -0700522 for target in self._slave_targets + [self._target]:
David James4058b0d2011-12-08 21:24:50 -0800523 url_suffix = _REL_HOST_PATH % {'version': version,
524 'host_arch': _HOST_ARCH,
525 'target': target}
David Jamese2488642011-11-14 16:15:20 -0800526 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James05bcb2b2011-02-09 09:25:47 -0800527
David James4058b0d2011-12-08 21:24:50 -0800528 if self._target == target and not self._skip_upload and not self._debug:
David Jamese2488642011-11-14 16:15:20 -0800529 # Upload prebuilts.
530 package_path = os.path.join(self._build_path, _HOST_PACKAGES_PATH)
531 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8ece7ee2011-06-29 16:02:30 -0700532
David Jamese2488642011-11-14 16:15:20 -0800533 # Record URL where prebuilts were uploaded.
534 binhost_urls.append('%s/%s/' % (self._binhost_base_url.rstrip('/'),
535 packages_url_suffix.rstrip('/')))
536
David James20b2b6f2011-11-18 15:11:58 -0800537 binhost = ' '.join(binhost_urls)
David James8ece7ee2011-06-29 16:02:30 -0700538 if git_sync:
539 git_file = os.path.join(self._build_path,
David James4058b0d2011-12-08 21:24:50 -0800540 _PREBUILT_MAKE_CONF[_HOST_ARCH])
David Jamese2488642011-11-14 16:15:20 -0800541 RevGitFile(git_file, binhost, key=key, dryrun=self._debug)
David James8ece7ee2011-06-29 16:02:30 -0700542 if sync_binhost_conf:
David James32b0b2f2011-07-13 20:56:50 -0700543 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James4058b0d2011-12-08 21:24:50 -0800544 'host', '%s-%s.conf' % (_HOST_ARCH, key))
David Jamese2488642011-11-14 16:15:20 -0800545 UpdateBinhostConfFile(binhost_conf, key, binhost)
David Jamesc0f158a2011-02-22 16:07:29 -0800546
Brian Harringc274d692012-05-12 00:24:26 -0700547 def _SyncBoardPrebuilts(self, version, key, git_sync, sync_binhost_conf,
548 upload_board_tarball):
David Jamesc0f158a2011-02-22 16:07:29 -0800549 """Synchronize board prebuilt files.
550
551 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800552 version: A unique string, intended to be included in the upload path,
553 which identifies the version number of the uploaded prebuilts.
554 key: The variable key to update in the git file.
555 git_sync: If set, update make.conf of target to reference the latest
556 prebuilt packages generated here.
557 sync_binhost_conf: If set, update binhost config file in
558 chromiumos-overlay for the current board.
David James8fa34ea2011-04-15 13:00:20 -0700559 upload_board_tarball: Include a tarball of the board in our upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800560 """
Brian Harringc274d692012-05-12 00:24:26 -0700561 for target in self._slave_targets + [self._target]:
David Jamese2488642011-11-14 16:15:20 -0800562 board_path = os.path.join(self._build_path,
David James4058b0d2011-12-08 21:24:50 -0800563 _BOARD_PATH % {'board': target.board_variant})
David Jamese2488642011-11-14 16:15:20 -0800564 package_path = os.path.join(board_path, 'packages')
David James4058b0d2011-12-08 21:24:50 -0800565 url_suffix = _REL_BOARD_PATH % {'target': target, 'version': version}
David Jamese2488642011-11-14 16:15:20 -0800566 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James8fa34ea2011-04-15 13:00:20 -0700567
David James4058b0d2011-12-08 21:24:50 -0800568 if self._target == target and not self._skip_upload and not self._debug:
David Jamese2488642011-11-14 16:15:20 -0800569 # Upload board tarballs in the background.
570 if upload_board_tarball:
571 tar_process = multiprocessing.Process(target=self._UploadBoardTarball,
572 args=(board_path, url_suffix,
573 version))
574 tar_process.start()
David James8fa34ea2011-04-15 13:00:20 -0700575
David Jamese2488642011-11-14 16:15:20 -0800576 # Upload prebuilts.
577 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8fa34ea2011-04-15 13:00:20 -0700578
David Jamese2488642011-11-14 16:15:20 -0800579 # Make sure we finished uploading the board tarballs.
580 if upload_board_tarball:
581 tar_process.join()
582 assert tar_process.exitcode == 0
583 # TODO(zbehan): This should be done cleaner.
David James4058b0d2011-12-08 21:24:50 -0800584 if target.board == 'amd64-host':
David Jamese2488642011-11-14 16:15:20 -0800585 sdk_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James8ece7ee2011-06-29 16:02:30 -0700586 'host/sdk_version.conf')
David Jamese2488642011-11-14 16:15:20 -0800587 RevGitFile(sdk_conf, version.strip('chroot-'),
588 key='SDK_LATEST_VERSION', dryrun=self._debug)
David Jamesc0f158a2011-02-22 16:07:29 -0800589
David Jamese2488642011-11-14 16:15:20 -0800590 # Record URL where prebuilts were uploaded.
591 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
592 packages_url_suffix.rstrip('/'))
593
594 if git_sync:
David James4058b0d2011-12-08 21:24:50 -0800595 git_file = DeterminePrebuiltConfFile(self._build_path, target)
David Jamese2488642011-11-14 16:15:20 -0800596 RevGitFile(git_file, url_value, key=key, dryrun=self._debug)
597 if sync_binhost_conf:
598 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James4058b0d2011-12-08 21:24:50 -0800599 'target', '%s-%s.conf' % (target, key))
David Jamese2488642011-11-14 16:15:20 -0800600 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800601
602
Brian Harringc274d692012-05-12 00:24:26 -0700603def usage(parser, msg):
David James8c846492011-01-25 17:07:29 -0800604 """Display usage message and parser help then exit with 1."""
605 print >> sys.stderr, msg
606 parser.print_help()
607 sys.exit(1)
608
David James4058b0d2011-12-08 21:24:50 -0800609
Brian Harringc274d692012-05-12 00:24:26 -0700610def add_slave_board(_option, _opt_str, value, parser):
David James4058b0d2011-12-08 21:24:50 -0800611 parser.values.slave_targets.append(BuildTarget(value))
612
613
Brian Harringc274d692012-05-12 00:24:26 -0700614def add_slave_profile(_option, _opt_str, value, parser):
David James4058b0d2011-12-08 21:24:50 -0800615 if not parser.values.slave_targets:
Brian Harringc274d692012-05-12 00:24:26 -0700616 usage(parser, 'Must specify --slave-board before --slave-profile')
David James4058b0d2011-12-08 21:24:50 -0800617 if parser.values.slave_targets[-1].profile is not None:
Brian Harringc274d692012-05-12 00:24:26 -0700618 usage(parser, 'Cannot specify --slave-profile twice for same board')
David James4058b0d2011-12-08 21:24:50 -0800619 parser.values.slave_targets[-1].profile = value
620
621
David Jamesc0f158a2011-02-22 16:07:29 -0800622def ParseOptions():
David James8c846492011-01-25 17:07:29 -0800623 parser = optparse.OptionParser()
624 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
625 default=_BINHOST_BASE_URL,
626 help='Base URL to use for binhost in make.conf updates')
627 parser.add_option('', '--previous-binhost-url', action='append',
628 default=[], dest='previous_binhost_url',
629 help='Previous binhost URL')
630 parser.add_option('-b', '--board', dest='board', default=None,
631 help='Board type that was built on this machine')
David James4058b0d2011-12-08 21:24:50 -0800632 parser.add_option('', '--profile', dest='profile', default=None,
633 help='Profile that was built on this machine')
634 parser.add_option('', '--slave-board', default=[], action='callback',
635 dest='slave_targets', type='string',
Brian Harringc274d692012-05-12 00:24:26 -0700636 callback=add_slave_board,
David James4058b0d2011-12-08 21:24:50 -0800637 help='Board type that was built on a slave machine. To '
638 'add a profile to this board, use --slave-profile.')
639 parser.add_option('', '--slave-profile', action='callback', type='string',
Brian Harringc274d692012-05-12 00:24:26 -0700640 callback=add_slave_profile,
David James4058b0d2011-12-08 21:24:50 -0800641 help='Board profile that was built on a slave machine. '
642 'Applies to previous slave board.')
David James8c846492011-01-25 17:07:29 -0800643 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800644 help='Path to the directory containing the chroot')
David James615e5b52011-06-03 11:10:15 -0700645 parser.add_option('', '--packages', action='append',
646 default=[], dest='packages',
647 help='Only include the specified packages. '
648 '(Default is to include all packages.)')
David James8c846492011-01-25 17:07:29 -0800649 parser.add_option('-s', '--sync-host', dest='sync_host',
650 default=False, action='store_true',
651 help='Sync host prebuilts')
652 parser.add_option('-g', '--git-sync', dest='git_sync',
653 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800654 help='Enable git version sync (This commits to a repo.) '
655 'This is used by full builders to commit directly '
656 'to board overlays.')
David James8c846492011-01-25 17:07:29 -0800657 parser.add_option('-u', '--upload', dest='upload',
658 default=None,
659 help='Upload location')
660 parser.add_option('-V', '--prepend-version', dest='prepend_version',
661 default=None,
662 help='Add an identifier to the front of the version')
663 parser.add_option('-f', '--filters', dest='filters', action='store_true',
664 default=False,
665 help='Turn on filtering of private ebuild packages')
666 parser.add_option('-k', '--key', dest='key',
667 default='PORTAGE_BINHOST',
668 help='Key to update in make.conf / binhost.conf')
David James8ece7ee2011-06-29 16:02:30 -0700669 parser.add_option('', '--set-version', dest='set_version',
670 default=None,
671 help='Specify the version string')
David James8c846492011-01-25 17:07:29 -0800672 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
673 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800674 help='Update binhost.conf in chromiumos-overlay or '
675 'chromeos-overlay. Commit the changes, but don\'t '
676 'push them. This is used for preflight binhosts.')
David James32b0b2f2011-07-13 20:56:50 -0700677 parser.add_option('', '--binhost-conf-dir', dest='binhost_conf_dir',
678 default=_BINHOST_CONF_DIR,
679 help='Directory to commit binhost config with '
680 '--sync-binhost-conf.')
David Jamesfd0b0852011-02-23 11:15:36 -0800681 parser.add_option('-P', '--private', dest='private', action='store_true',
682 default=False, help='Mark gs:// uploads as private.')
David James8ece7ee2011-06-29 16:02:30 -0700683 parser.add_option('', '--skip-upload', dest='skip_upload',
684 action='store_true', default=False,
685 help='Skip upload step.')
David James8fa34ea2011-04-15 13:00:20 -0700686 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
687 action='store_true', default=False,
688 help='Upload board tarball to Google Storage.')
David James27fa7d12011-06-29 17:24:14 -0700689 parser.add_option('', '--debug', dest='debug',
690 action='store_true', default=False,
691 help='Don\'t push or upload prebuilts.')
David James8c846492011-01-25 17:07:29 -0800692
693 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800694 if not options.build_path:
Brian Harringc274d692012-05-12 00:24:26 -0700695 usage(parser, 'Error: you need provide a chroot path')
David James8ece7ee2011-06-29 16:02:30 -0700696 if not options.upload and not options.skip_upload:
Brian Harringc274d692012-05-12 00:24:26 -0700697 usage(parser, 'Error: you need to provide an upload location using -u')
David James8ece7ee2011-06-29 16:02:30 -0700698 if not options.set_version and options.skip_upload:
Brian Harringc274d692012-05-12 00:24:26 -0700699 usage(parser, 'Error: If you are using --skip-upload, you must specify a '
David James8ece7ee2011-06-29 16:02:30 -0700700 'version number using --set-version.')
David James9417f272011-05-26 13:24:47 -0700701 if args:
Brian Harringc274d692012-05-12 00:24:26 -0700702 usage(parser, 'Error: invalid arguments passed to prebuilt.py: %r' % args)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700703
Brian Harringc274d692012-05-12 00:24:26 -0700704 options.target = BuildTarget(options.board, options.profile)
705 if options.target in options.slave_targets:
706 usage(parser, 'Error: --board/--profile must not also be a slave target.')
David Jamese2488642011-11-14 16:15:20 -0800707
David James4058b0d2011-12-08 21:24:50 -0800708 if len(set(options.slave_targets)) != len(options.slave_targets):
Brian Harringc274d692012-05-12 00:24:26 -0700709 usage(parser, 'Error: --slave-boards must not have duplicates.')
David Jamese2488642011-11-14 16:15:20 -0800710
David James4058b0d2011-12-08 21:24:50 -0800711 if options.slave_targets and options.git_sync:
Brian Harringc274d692012-05-12 00:24:26 -0700712 usage(parser, 'Error: --slave-boards is not compatible with --git-sync')
David Jamese2488642011-11-14 16:15:20 -0800713
David James8ece7ee2011-06-29 16:02:30 -0700714 if (options.upload_board_tarball and options.skip_upload and
715 options.board == 'amd64-host'):
Brian Harringc274d692012-05-12 00:24:26 -0700716 usage(parser, 'Error: --skip-upload is not compatible with '
David James8ece7ee2011-06-29 16:02:30 -0700717 '--upload-board-tarball and --board=amd64-host')
David James8fa34ea2011-04-15 13:00:20 -0700718
David James8ece7ee2011-06-29 16:02:30 -0700719 if (options.upload_board_tarball and not options.skip_upload and
720 not options.upload.startswith('gs://')):
Brian Harringc274d692012-05-12 00:24:26 -0700721 usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
David James8fa34ea2011-04-15 13:00:20 -0700722 '--upload must be a gs:// URL.')
723
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700724 if options.private:
725 if options.sync_host:
Brian Harringc274d692012-05-12 00:24:26 -0700726 usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700727 'together, we do not support private host prebuilts')
728
David James8ece7ee2011-06-29 16:02:30 -0700729 if not options.upload or not options.upload.startswith('gs://'):
Brian Harringc274d692012-05-12 00:24:26 -0700730 usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700731 '--upload must be a gs:// URL.')
732
733 if options.binhost_base_url != _BINHOST_BASE_URL:
Brian Harringc274d692012-05-12 00:24:26 -0700734 usage(parser, 'Error: when using --private the --binhost-base-url '
David Jamese2488642011-11-14 16:15:20 -0800735 'is automatically derived.')
David James27fa7d12011-06-29 17:24:14 -0700736
Brian Harringc274d692012-05-12 00:24:26 -0700737 return options
David Jamesc0f158a2011-02-22 16:07:29 -0800738
739def main():
David Jamesdb401072011-06-10 12:17:16 -0700740 # Set umask to a sane value so that files created as root are readable.
741 os.umask(022)
742
Brian Harringc274d692012-05-12 00:24:26 -0700743 options = ParseOptions()
David Jamesc0f158a2011-02-22 16:07:29 -0800744
David James05bcb2b2011-02-09 09:25:47 -0800745 # Calculate a list of Packages index files to compare against. Whenever we
746 # upload a package, we check to make sure it's not already stored in one of
747 # the packages files we uploaded. This list of packages files might contain
748 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800749 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800750
David James8ece7ee2011-06-29 16:02:30 -0700751 if options.set_version:
752 version = options.set_version
753 else:
754 version = GetVersion()
David Jamesc0f158a2011-02-22 16:07:29 -0800755 if options.prepend_version:
756 version = '%s-%s' % (options.prepend_version, version)
757
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700758 acl = 'public-read'
759 binhost_base_url = options.binhost_base_url
760
Brian Harringc274d692012-05-12 00:24:26 -0700761 if options.private:
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700762 binhost_base_url = options.upload
763 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
Brian Harringc274d692012-05-12 00:24:26 -0700764 options.target)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700765 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
766
767 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David James615e5b52011-06-03 11:10:15 -0700768 pkg_indexes, options.build_path,
David James27fa7d12011-06-29 17:24:14 -0700769 options.packages, options.skip_upload,
David Jamese2488642011-11-14 16:15:20 -0800770 options.binhost_conf_dir, options.debug,
Brian Harringc274d692012-05-12 00:24:26 -0700771 options.target, options.slave_targets)
David Jamesc0f158a2011-02-22 16:07:29 -0800772
David James8c846492011-01-25 17:07:29 -0800773 if options.sync_host:
Brian Harringc274d692012-05-12 00:24:26 -0700774 uploader._SyncHostPrebuilts(version, options.key, options.git_sync,
775 options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800776
777 if options.board:
Brian Harringc274d692012-05-12 00:24:26 -0700778 uploader._SyncBoardPrebuilts(version, options.key, options.git_sync,
779 options.sync_binhost_conf,
780 options.upload_board_tarball)
David James8c846492011-01-25 17:07:29 -0800781
782if __name__ == '__main__':
783 main()