blob: dcbc82bec7b3f4634ed179e81157d857ed3b65f6 [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
13import time
14
Chris Sosa471532a2011-02-01 15:10:06 -080015if __name__ == '__main__':
16 import constants
17 sys.path.append(constants.SOURCE_ROOT)
18
David James8c846492011-01-25 17:07:29 -080019from chromite.lib import cros_build_lib
Chris Sosac13bba52011-05-24 15:14:09 -070020from chromite.lib.binpkg import (GrabLocalPackageIndex, GrabRemotePackageIndex)
David James8c846492011-01-25 17:07:29 -080021"""
22This script is used to upload host prebuilts as well as board BINHOSTS.
23
24If the URL starts with 'gs://', we upload using gsutil to Google Storage.
25Otherwise, rsync is used.
26
27After a build is successfully uploaded a file is updated with the proper
28BINHOST version as well as the target board. This file is defined in GIT_FILE
29
30
31To read more about prebuilts/binhost binary packages please refer to:
32http://sites/chromeos/for-team-members/engineering/releng/prebuilt-binaries-for-streamlining-the-build-process
33
34
35Example of uploading prebuilt amd64 host files to Google Storage:
36./prebuilt.py -p /b/cbuild/build -s -u gs://chromeos-prebuilt
37
38Example of uploading x86-dogfood binhosts to Google Storage:
39./prebuilt.py -b x86-dogfood -p /b/cbuild/build/ -u gs://chromeos-prebuilt -g
40
41Example of uploading prebuilt amd64 host files using rsync:
42./prebuilt.py -p /b/cbuild/build -s -u codf30.jail:/tmp
43"""
44
David James8c846492011-01-25 17:07:29 -080045_RETRIES = 3
46_GSUTIL_BIN = '/b/build/third_party/gsutil/gsutil'
47_HOST_PACKAGES_PATH = 'chroot/var/lib/portage/pkgs'
David James05bcb2b2011-02-09 09:25:47 -080048_CATEGORIES_PATH = 'chroot/etc/portage/categories'
David James615e5b52011-06-03 11:10:15 -070049_PYM_PATH = 'chroot/usr/lib/portage/pym'
David James8c846492011-01-25 17:07:29 -080050_HOST_TARGET = 'amd64'
51_BOARD_PATH = 'chroot/build/%(board)s'
David James8fa34ea2011-04-15 13:00:20 -070052# board/board-target/version/'
53_REL_BOARD_PATH = 'board/%(board)s/%(version)s'
54# host/host-target/version/'
55_REL_HOST_PATH = 'host/%(target)s/%(version)s'
David James8c846492011-01-25 17:07:29 -080056# Private overlays to look at for builds to filter
57# relative to build path
58_PRIVATE_OVERLAY_DIR = 'src/private-overlays'
Scott Zawalskiab1bed32011-03-16 15:24:24 -070059_GOOGLESTORAGE_ACL_FILE = 'googlestorage_acl.xml'
David James8c846492011-01-25 17:07:29 -080060_BINHOST_BASE_URL = 'http://commondatastorage.googleapis.com/chromeos-prebuilt'
61_PREBUILT_BASE_DIR = 'src/third_party/chromiumos-overlay/chromeos/config/'
62# Created in the event of new host targets becoming available
63_PREBUILT_MAKE_CONF = {'amd64': os.path.join(_PREBUILT_BASE_DIR,
64 'make.conf.amd64-host')}
65_BINHOST_CONF_DIR = 'src/third_party/chromiumos-overlay/chromeos/binhost'
66
67
David James8c846492011-01-25 17:07:29 -080068class UploadFailed(Exception):
69 """Raised when one of the files uploaded failed."""
70 pass
71
72class UnknownBoardFormat(Exception):
73 """Raised when a function finds an unknown board format."""
74 pass
75
David James8c846492011-01-25 17:07:29 -080076
77def UpdateLocalFile(filename, value, key='PORTAGE_BINHOST'):
78 """Update the key in file with the value passed.
79 File format:
80 key="value"
81 Note quotes are added automatically
82
83 Args:
84 filename: Name of file to modify.
85 value: Value to write with the key.
86 key: The variable key to update. (Default: PORTAGE_BINHOST)
87 """
88 if os.path.exists(filename):
89 file_fh = open(filename)
90 else:
91 file_fh = open(filename, 'w+')
92 file_lines = []
93 found = False
94 keyval_str = '%(key)s=%(value)s'
95 for line in file_fh:
96 # Strip newlines from end of line. We already add newlines below.
97 line = line.rstrip("\n")
98
99 if len(line.split('=')) != 2:
100 # Skip any line that doesn't fit key=val.
101 file_lines.append(line)
102 continue
103
104 file_var, file_val = line.split('=')
105 if file_var == key:
106 found = True
107 print 'Updating %s=%s to %s="%s"' % (file_var, file_val, key, value)
108 value = '"%s"' % value
109 file_lines.append(keyval_str % {'key': key, 'value': value})
110 else:
111 file_lines.append(keyval_str % {'key': file_var, 'value': file_val})
112
113 if not found:
114 file_lines.append(keyval_str % {'key': key, 'value': value})
115
116 file_fh.close()
117 # write out new file
118 new_file_fh = open(filename, 'w')
119 new_file_fh.write('\n'.join(file_lines) + '\n')
120 new_file_fh.close()
121
122
David James27fa7d12011-06-29 17:24:14 -0700123def RevGitFile(filename, value, retries=5, key='PORTAGE_BINHOST', dryrun=False):
David James8c846492011-01-25 17:07:29 -0800124 """Update and push the git file.
125
126 Args:
127 filename: file to modify that is in a git repo already
128 value: string representing the version of the prebuilt that has been
129 uploaded.
130 retries: The number of times to retry before giving up, default: 5
131 key: The variable key to update in the git file.
132 (Default: PORTAGE_BINHOST)
133 """
134 prebuilt_branch = 'prebuilt_branch'
David James1b6e67a2011-05-19 21:32:38 -0700135 cwd = os.path.abspath(os.path.dirname(filename))
136 commit = cros_build_lib.RunCommand(['git', 'rev-parse', 'HEAD'], cwd=cwd,
Peter Mayofe0e6872011-04-20 00:52:08 -0400137 redirect_stdout=True).output.rstrip()
Peter Mayo193f68f2011-04-19 19:08:21 -0400138 git_ssh_config_cmd = [
139 'git',
140 'config',
Chris Sosac13bba52011-05-24 15:14:09 -0700141 'url.ssh://gerrit.chromium.org:29418.pushinsteadof',
David James1b6e67a2011-05-19 21:32:38 -0700142 'http://git.chromium.org']
143 cros_build_lib.RunCommand(git_ssh_config_cmd, cwd=cwd)
144 cros_build_lib.RunCommand(['git', 'remote', 'update'], cwd=cwd)
145 cros_build_lib.RunCommand(['repo', 'start', prebuilt_branch, '.'], cwd=cwd)
David James6181b892011-06-08 16:45:07 -0700146
147 # We want to push our changes to this file to tip of tree, so we should
148 # make sure the branch is at tip of tree before we apply our change.
149 push_branch = '%s/%s' % cros_build_lib.GetPushBranch(prebuilt_branch, cwd)
150 cros_build_lib.RunCommand(['git', 'reset', '--hard', push_branch], cwd=cwd)
151
David James8c846492011-01-25 17:07:29 -0800152 description = 'Update %s="%s" in %s' % (key, value, filename)
153 print description
154 try:
155 UpdateLocalFile(filename, value, key)
David James1b6e67a2011-05-19 21:32:38 -0700156 cros_build_lib.RunCommand(['git', 'config', 'push.default', 'tracking'],
157 cwd=cwd)
158 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
159 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James27fa7d12011-06-29 17:24:14 -0700160 cros_build_lib.GitPushWithRetry(prebuilt_branch, cwd=cwd, dryrun=dryrun)
David James8c846492011-01-25 17:07:29 -0800161 finally:
David James1b6e67a2011-05-19 21:32:38 -0700162 cros_build_lib.RunCommand(['git', 'checkout', commit], cwd=cwd)
163 cros_build_lib.RunCommand(['repo', 'abandon', 'prebuilt_branch', '.'],
164 cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800165
166
167def GetVersion():
168 """Get the version to put in LATEST and update the git version with."""
169 return datetime.datetime.now().strftime('%d.%m.%y.%H%M%S')
170
171
Peter Mayo193f68f2011-04-19 19:08:21 -0400172def _RetryRun(cmd, print_cmd=True, cwd=None):
David James8c846492011-01-25 17:07:29 -0800173 """Run the specified command, retrying if necessary.
174
175 Args:
176 cmd: The command to run.
177 print_cmd: Whether to print out the cmd.
178 shell: Whether to treat the command as a shell.
179 cwd: Working directory to run command in.
180
181 Returns:
182 True if the command succeeded. Otherwise, returns False.
183 """
184
185 # TODO(scottz): port to use _Run or similar when it is available in
186 # cros_build_lib.
187 for attempt in range(_RETRIES):
188 try:
Peter Mayo193f68f2011-04-19 19:08:21 -0400189 output = cros_build_lib.RunCommand(cmd, print_cmd=print_cmd,
David James8c846492011-01-25 17:07:29 -0800190 cwd=cwd)
191 return True
192 except cros_build_lib.RunCommandError:
Peter Mayo193f68f2011-04-19 19:08:21 -0400193 print 'Failed to run %r' % cmd
David James8c846492011-01-25 17:07:29 -0800194 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400195 print 'Retry failed run %r, giving up' % cmd
David James8c846492011-01-25 17:07:29 -0800196 return False
197
198
199def _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
Peter Mayo193f68f2011-04-19 19:08:21 -0400229 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.
Peter Mayo193f68f2011-04-19 19:08:21 -0400234 _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 James8c846492011-01-25 17:07:29 -0800298 cmd = ['./cros_overlay_list']
299 if re.match('.*?_.*', target):
300 (board, variant) = target.split('_')
301 cmd += ['--board', board, '--variant', variant]
302 elif re.match('.*?-\w+', target):
303 cmd += ['--board', target]
Scott Zawalski1c660ea2011-06-24 19:12:58 -0700304 elif target == 'stumpy':
305 cmd += ['--board', target]
David James8c846492011-01-25 17:07:29 -0800306 else:
307 raise UnknownBoardFormat('Unknown format: %s' % target)
308
309 cmd_output = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
310 cwd=script_dir)
311 # We only care about the last entry
312 return cmd_output.output.splitlines().pop()
313
314
315def DeterminePrebuiltConfFile(build_path, target):
316 """Determine the prebuilt.conf file that needs to be updated for prebuilts.
317
318 Args:
319 build_path: The path to the root of the build directory
320 target: String representation of the board. This includes host and board
321 targets
322
323 Returns
324 A string path to a prebuilt.conf file to be updated.
325 """
326 if _HOST_TARGET == target:
327 # We are host.
328 # Without more examples of hosts this is a kludge for now.
329 # TODO(Scottz): as new host targets come online expand this to
330 # work more like boards.
Chris Sosa471532a2011-02-01 15:10:06 -0800331 make_path = _PREBUILT_MAKE_CONF[target]
David James8c846492011-01-25 17:07:29 -0800332 else:
333 # We are a board
334 board = GetBoardPathFromCrosOverlayList(build_path, target)
335 make_path = os.path.join(board, 'prebuilt.conf')
336
337 return make_path
338
339
340def UpdateBinhostConfFile(path, key, value):
341 """Update binhost config file file with key=value.
342
343 Args:
344 path: Filename to update.
345 key: Key to update.
346 value: New value for key.
347 """
348 cwd = os.path.dirname(os.path.abspath(path))
349 filename = os.path.basename(path)
350 if not os.path.isdir(cwd):
351 os.makedirs(cwd)
352 if not os.path.isfile(path):
353 config_file = file(path, 'w')
David James8c846492011-01-25 17:07:29 -0800354 config_file.close()
355 UpdateLocalFile(path, value, key)
Chris Sosac13bba52011-05-24 15:14:09 -0700356 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800357 description = 'Update %s=%s in %s' % (key, value, filename)
Peter Mayo193f68f2011-04-19 19:08:21 -0400358 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800359
360
David Jamesce093af2011-02-23 15:21:58 -0800361def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800362 """Grab all of the packages files associated with a list of binhost_urls.
363
David James05bcb2b2011-02-09 09:25:47 -0800364 Args:
365 binhost_urls: The URLs for the directories containing the Packages files we
366 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800367
368 Returns:
369 A list of PackageIndex objects.
370 """
371 pkg_indexes = []
372 for url in binhost_urls:
373 pkg_index = GrabRemotePackageIndex(url)
374 if pkg_index:
375 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800376 return pkg_indexes
377
378
David James05bcb2b2011-02-09 09:25:47 -0800379
David Jamesc0f158a2011-02-22 16:07:29 -0800380class PrebuiltUploader(object):
381 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800382
David James615e5b52011-06-03 11:10:15 -0700383 def __init__(self, upload_location, acl, binhost_base_url,
David James27fa7d12011-06-29 17:24:14 -0700384 pkg_indexes, build_path, packages, skip_upload, debug):
David Jamesc0f158a2011-02-22 16:07:29 -0800385 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800386
David Jamesc0f158a2011-02-22 16:07:29 -0800387 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800388
David Jamesc0f158a2011-02-22 16:07:29 -0800389 Args:
390 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800391 acl: The canned acl used for uploading to Google Storage. acl can be one
392 of: "public-read", "public-read-write", "authenticated-read",
393 "bucket-owner-read", "bucket-owner-full-control", or "private". If
394 we are not uploading to Google Storage, this parameter is unused.
395 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800396 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
397 uploading duplicate files, we just link to the old files.
David James615e5b52011-06-03 11:10:15 -0700398 build_path: The path to the directory containing the chroot.
399 packages: Packages to upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800400 """
401 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800402 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800403 self._binhost_base_url = binhost_base_url
404 self._pkg_indexes = pkg_indexes
David James615e5b52011-06-03 11:10:15 -0700405 self._build_path = build_path
406 self._packages = set(packages)
David James8ece7ee2011-06-29 16:02:30 -0700407 self._skip_upload = skip_upload
David James27fa7d12011-06-29 17:24:14 -0700408 self._debug = debug
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))
414 sys.path.append(pym_path)
415 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:
Peter Mayo193f68f2011-04-19 19:08:21 -0400461 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)
478 cmd = ['sudo', 'tar', '-I', 'pbzip2', '-cf', tarfile]
479 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('.')
484 cros_build_lib.RunCommand(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:
496 cros_build_lib.RunCommand(['sudo', 'rm', '-rf', tmpdir], cwd=cwd)
497
David James615e5b52011-06-03 11:10:15 -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 """
518 # Upload prebuilts.
David James615e5b52011-06-03 11:10:15 -0700519 package_path = os.path.join(self._build_path, _HOST_PACKAGES_PATH)
David Jamesc0f158a2011-02-22 16:07:29 -0800520 url_suffix = _REL_HOST_PATH % {'version': version, 'target': _HOST_TARGET}
David James8fa34ea2011-04-15 13:00:20 -0700521 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James05bcb2b2011-02-09 09:25:47 -0800522
David James27fa7d12011-06-29 17:24:14 -0700523 if not self._skip_upload and not self._debug:
David James8ece7ee2011-06-29 16:02:30 -0700524 self._UploadPrebuilt(package_path, packages_url_suffix)
525
526 # Record URL where prebuilts were uploaded.
527 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
528 packages_url_suffix.rstrip('/'))
529 if git_sync:
530 git_file = os.path.join(self._build_path,
531 _PREBUILT_MAKE_CONF[_HOST_TARGET])
David James27fa7d12011-06-29 17:24:14 -0700532 RevGitFile(git_file, url_value, key=key, dryrun=self._debug)
David James8ece7ee2011-06-29 16:02:30 -0700533 if sync_binhost_conf:
534 binhost_conf = os.path.join(self._build_path, _BINHOST_CONF_DIR,
535 'host', '%s-%s.conf' % (_HOST_TARGET, key))
536 UpdateBinhostConfFile(binhost_conf, key, url_value)
David Jamesc0f158a2011-02-22 16:07:29 -0800537
David James615e5b52011-06-03 11:10:15 -0700538 def _SyncBoardPrebuilts(self, board, version, key, git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700539 sync_binhost_conf, upload_board_tarball):
David Jamesc0f158a2011-02-22 16:07:29 -0800540 """Synchronize board prebuilt files.
541
542 Args:
543 board: The board to upload to Google Storage.
David Jamesc0f158a2011-02-22 16:07:29 -0800544 version: A unique string, intended to be included in the upload path,
545 which identifies the version number of the uploaded prebuilts.
546 key: The variable key to update in the git file.
547 git_sync: If set, update make.conf of target to reference the latest
548 prebuilt packages generated here.
549 sync_binhost_conf: If set, update binhost config file in
550 chromiumos-overlay for the current board.
David James8fa34ea2011-04-15 13:00:20 -0700551 upload_board_tarball: Include a tarball of the board in our upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800552 """
David James615e5b52011-06-03 11:10:15 -0700553 board_path = os.path.join(self._build_path, _BOARD_PATH % {'board': board})
David Jamesc0f158a2011-02-22 16:07:29 -0800554 package_path = os.path.join(board_path, 'packages')
555 url_suffix = _REL_BOARD_PATH % {'board': board, 'version': version}
David James8fa34ea2011-04-15 13:00:20 -0700556 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
557
David James27fa7d12011-06-29 17:24:14 -0700558 if not self._skip_upload and not self._debug:
David James8ece7ee2011-06-29 16:02:30 -0700559 # Upload board tarballs in the background.
560 if upload_board_tarball:
561 tar_process = multiprocessing.Process(target=self._UploadBoardTarball,
562 args=(board_path, url_suffix,
563 version))
564 tar_process.start()
David James8fa34ea2011-04-15 13:00:20 -0700565
David James8ece7ee2011-06-29 16:02:30 -0700566 # Upload prebuilts.
567 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8fa34ea2011-04-15 13:00:20 -0700568
David James8ece7ee2011-06-29 16:02:30 -0700569 # Make sure we finished uploading the board tarballs.
570 if upload_board_tarball:
571 tar_process.join()
572 assert tar_process.exitcode == 0
573 # TODO(zbehan): This should be done cleaner.
574 if board == 'amd64-host':
575 sdk_conf = os.path.join(self._build_path, _BINHOST_CONF_DIR,
576 'host/sdk_version.conf')
577 RevGitFile(sdk_conf, version.strip('chroot-'),
David James27fa7d12011-06-29 17:24:14 -0700578 key='SDK_LATEST_VERSION', dryrun=self._debug)
David Jamesc0f158a2011-02-22 16:07:29 -0800579
David James8ece7ee2011-06-29 16:02:30 -0700580 # Record URL where prebuilts were uploaded.
581 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
582 packages_url_suffix.rstrip('/'))
583 if git_sync:
584 git_file = DeterminePrebuiltConfFile(self._build_path, board)
David James27fa7d12011-06-29 17:24:14 -0700585 RevGitFile(git_file, url_value, key=key, dryrun=self._debug)
David James8ece7ee2011-06-29 16:02:30 -0700586 if sync_binhost_conf:
587 binhost_conf = os.path.join(self._build_path, _BINHOST_CONF_DIR,
588 'target', '%s-%s.conf' % (board, key))
589 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800590
591
David James8c846492011-01-25 17:07:29 -0800592def usage(parser, msg):
593 """Display usage message and parser help then exit with 1."""
594 print >> sys.stderr, msg
595 parser.print_help()
596 sys.exit(1)
597
David Jamesc0f158a2011-02-22 16:07:29 -0800598def ParseOptions():
David James8c846492011-01-25 17:07:29 -0800599 parser = optparse.OptionParser()
600 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
601 default=_BINHOST_BASE_URL,
602 help='Base URL to use for binhost in make.conf updates')
603 parser.add_option('', '--previous-binhost-url', action='append',
604 default=[], dest='previous_binhost_url',
605 help='Previous binhost URL')
606 parser.add_option('-b', '--board', dest='board', default=None,
607 help='Board type that was built on this machine')
608 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800609 help='Path to the directory containing the chroot')
David James615e5b52011-06-03 11:10:15 -0700610 parser.add_option('', '--packages', action='append',
611 default=[], dest='packages',
612 help='Only include the specified packages. '
613 '(Default is to include all packages.)')
David James8c846492011-01-25 17:07:29 -0800614 parser.add_option('-s', '--sync-host', dest='sync_host',
615 default=False, action='store_true',
616 help='Sync host prebuilts')
617 parser.add_option('-g', '--git-sync', dest='git_sync',
618 default=False, action='store_true',
619 help='Enable git version sync (This commits to a repo)')
620 parser.add_option('-u', '--upload', dest='upload',
621 default=None,
622 help='Upload location')
623 parser.add_option('-V', '--prepend-version', dest='prepend_version',
624 default=None,
625 help='Add an identifier to the front of the version')
626 parser.add_option('-f', '--filters', dest='filters', action='store_true',
627 default=False,
628 help='Turn on filtering of private ebuild packages')
629 parser.add_option('-k', '--key', dest='key',
630 default='PORTAGE_BINHOST',
631 help='Key to update in make.conf / binhost.conf')
David James8ece7ee2011-06-29 16:02:30 -0700632 parser.add_option('', '--set-version', dest='set_version',
633 default=None,
634 help='Specify the version string')
David James8c846492011-01-25 17:07:29 -0800635 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
636 default=False, action='store_true',
637 help='Update binhost.conf')
David Jamesfd0b0852011-02-23 11:15:36 -0800638 parser.add_option('-P', '--private', dest='private', action='store_true',
639 default=False, help='Mark gs:// uploads as private.')
David James8ece7ee2011-06-29 16:02:30 -0700640 parser.add_option('', '--skip-upload', dest='skip_upload',
641 action='store_true', default=False,
642 help='Skip upload step.')
David James8fa34ea2011-04-15 13:00:20 -0700643 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
644 action='store_true', default=False,
645 help='Upload board tarball to Google Storage.')
David James27fa7d12011-06-29 17:24:14 -0700646 parser.add_option('', '--debug', dest='debug',
647 action='store_true', default=False,
648 help='Don\'t push or upload prebuilts.')
David James8c846492011-01-25 17:07:29 -0800649
650 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800651 if not options.build_path:
652 usage(parser, 'Error: you need provide a chroot path')
David James8ece7ee2011-06-29 16:02:30 -0700653 if not options.upload and not options.skip_upload:
David James8c846492011-01-25 17:07:29 -0800654 usage(parser, 'Error: you need to provide an upload location using -u')
David James8ece7ee2011-06-29 16:02:30 -0700655 if not options.set_version and options.skip_upload:
656 usage(parser, 'Error: If you are using --skip-upload, you must specify a '
657 'version number using --set-version.')
David James9417f272011-05-26 13:24:47 -0700658 if args:
659 usage(parser, 'Error: invalid arguments passed to prebuilt.py: %r' % args)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700660
David James8ece7ee2011-06-29 16:02:30 -0700661 if (options.upload_board_tarball and options.skip_upload and
662 options.board == 'amd64-host'):
663 usage(parser, 'Error: --skip-upload is not compatible with '
664 '--upload-board-tarball and --board=amd64-host')
David James8fa34ea2011-04-15 13:00:20 -0700665
David James8ece7ee2011-06-29 16:02:30 -0700666 if (options.upload_board_tarball and not options.skip_upload and
667 not options.upload.startswith('gs://')):
David James8fa34ea2011-04-15 13:00:20 -0700668 usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
669 '--upload must be a gs:// URL.')
670
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700671 if options.private:
672 if options.sync_host:
673 usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
674 'together, we do not support private host prebuilts')
675
David James8ece7ee2011-06-29 16:02:30 -0700676 if not options.upload or not options.upload.startswith('gs://'):
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700677 usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
678 '--upload must be a gs:// URL.')
679
680 if options.binhost_base_url != _BINHOST_BASE_URL:
681 usage(parser, 'Error: when using --private the --binhost-base-url '
682 'is automatically derived.')
David James27fa7d12011-06-29 17:24:14 -0700683
David Jamesc0f158a2011-02-22 16:07:29 -0800684 return options
685
686def main():
David Jamesdb401072011-06-10 12:17:16 -0700687 # Set umask to a sane value so that files created as root are readable.
688 os.umask(022)
689
David Jamesc0f158a2011-02-22 16:07:29 -0800690 options = ParseOptions()
691
David James05bcb2b2011-02-09 09:25:47 -0800692 # Calculate a list of Packages index files to compare against. Whenever we
693 # upload a package, we check to make sure it's not already stored in one of
694 # the packages files we uploaded. This list of packages files might contain
695 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800696 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800697
David James8ece7ee2011-06-29 16:02:30 -0700698 if options.set_version:
699 version = options.set_version
700 else:
701 version = GetVersion()
David Jamesc0f158a2011-02-22 16:07:29 -0800702 if options.prepend_version:
703 version = '%s-%s' % (options.prepend_version, version)
704
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700705 acl = 'public-read'
706 binhost_base_url = options.binhost_base_url
707
708 if options.private:
709 binhost_base_url = options.upload
710 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
711 options.board)
712 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
713
714 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David James615e5b52011-06-03 11:10:15 -0700715 pkg_indexes, options.build_path,
David James27fa7d12011-06-29 17:24:14 -0700716 options.packages, options.skip_upload,
717 options.debug)
David Jamesc0f158a2011-02-22 16:07:29 -0800718
David James8c846492011-01-25 17:07:29 -0800719 if options.sync_host:
David James615e5b52011-06-03 11:10:15 -0700720 uploader._SyncHostPrebuilts(version, options.key, options.git_sync,
721 options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800722
723 if options.board:
David James615e5b52011-06-03 11:10:15 -0700724 uploader._SyncBoardPrebuilts(options.board, version,
David Jamesc0f158a2011-02-22 16:07:29 -0800725 options.key, options.git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700726 options.sync_binhost_conf,
727 options.upload_board_tarball)
David James8c846492011-01-25 17:07:29 -0800728
729if __name__ == '__main__':
730 main()