blob: cdfa98d8cd870c1425f79e4069437d769b390874 [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
45# as per http://crosbug.com/5855 always filter the below packages
46_FILTER_PACKAGES = set()
47_RETRIES = 3
48_GSUTIL_BIN = '/b/build/third_party/gsutil/gsutil'
49_HOST_PACKAGES_PATH = 'chroot/var/lib/portage/pkgs'
David James05bcb2b2011-02-09 09:25:47 -080050_CATEGORIES_PATH = 'chroot/etc/portage/categories'
David James8c846492011-01-25 17:07:29 -080051_HOST_TARGET = 'amd64'
52_BOARD_PATH = 'chroot/build/%(board)s'
David James8fa34ea2011-04-15 13:00:20 -070053# board/board-target/version/'
54_REL_BOARD_PATH = 'board/%(board)s/%(version)s'
55# host/host-target/version/'
56_REL_HOST_PATH = 'host/%(target)s/%(version)s'
David James8c846492011-01-25 17:07:29 -080057# Private overlays to look at for builds to filter
58# relative to build path
59_PRIVATE_OVERLAY_DIR = 'src/private-overlays'
Scott Zawalskiab1bed32011-03-16 15:24:24 -070060_GOOGLESTORAGE_ACL_FILE = 'googlestorage_acl.xml'
David James8c846492011-01-25 17:07:29 -080061_BINHOST_BASE_URL = 'http://commondatastorage.googleapis.com/chromeos-prebuilt'
62_PREBUILT_BASE_DIR = 'src/third_party/chromiumos-overlay/chromeos/config/'
63# Created in the event of new host targets becoming available
64_PREBUILT_MAKE_CONF = {'amd64': os.path.join(_PREBUILT_BASE_DIR,
65 'make.conf.amd64-host')}
66_BINHOST_CONF_DIR = 'src/third_party/chromiumos-overlay/chromeos/binhost'
67
68
69class FiltersEmpty(Exception):
70 """Raised when filters are used but none are found."""
71 pass
72
73
74class UploadFailed(Exception):
75 """Raised when one of the files uploaded failed."""
76 pass
77
78class UnknownBoardFormat(Exception):
79 """Raised when a function finds an unknown board format."""
80 pass
81
David James8c846492011-01-25 17:07:29 -080082
83def UpdateLocalFile(filename, value, key='PORTAGE_BINHOST'):
84 """Update the key in file with the value passed.
85 File format:
86 key="value"
87 Note quotes are added automatically
88
89 Args:
90 filename: Name of file to modify.
91 value: Value to write with the key.
92 key: The variable key to update. (Default: PORTAGE_BINHOST)
93 """
94 if os.path.exists(filename):
95 file_fh = open(filename)
96 else:
97 file_fh = open(filename, 'w+')
98 file_lines = []
99 found = False
100 keyval_str = '%(key)s=%(value)s'
101 for line in file_fh:
102 # Strip newlines from end of line. We already add newlines below.
103 line = line.rstrip("\n")
104
105 if len(line.split('=')) != 2:
106 # Skip any line that doesn't fit key=val.
107 file_lines.append(line)
108 continue
109
110 file_var, file_val = line.split('=')
111 if file_var == key:
112 found = True
113 print 'Updating %s=%s to %s="%s"' % (file_var, file_val, key, value)
114 value = '"%s"' % value
115 file_lines.append(keyval_str % {'key': key, 'value': value})
116 else:
117 file_lines.append(keyval_str % {'key': file_var, 'value': file_val})
118
119 if not found:
120 file_lines.append(keyval_str % {'key': key, 'value': value})
121
122 file_fh.close()
123 # write out new file
124 new_file_fh = open(filename, 'w')
125 new_file_fh.write('\n'.join(file_lines) + '\n')
126 new_file_fh.close()
127
128
David James8c846492011-01-25 17:07:29 -0800129def RevGitFile(filename, value, retries=5, key='PORTAGE_BINHOST'):
130 """Update and push the git file.
131
132 Args:
133 filename: file to modify that is in a git repo already
134 value: string representing the version of the prebuilt that has been
135 uploaded.
136 retries: The number of times to retry before giving up, default: 5
137 key: The variable key to update in the git file.
138 (Default: PORTAGE_BINHOST)
139 """
140 prebuilt_branch = 'prebuilt_branch'
David James1b6e67a2011-05-19 21:32:38 -0700141 cwd = os.path.abspath(os.path.dirname(filename))
142 commit = cros_build_lib.RunCommand(['git', 'rev-parse', 'HEAD'], cwd=cwd,
Peter Mayofe0e6872011-04-20 00:52:08 -0400143 redirect_stdout=True).output.rstrip()
Peter Mayo193f68f2011-04-19 19:08:21 -0400144 git_ssh_config_cmd = [
145 'git',
146 'config',
Chris Sosac13bba52011-05-24 15:14:09 -0700147 'url.ssh://gerrit.chromium.org:29418.pushinsteadof',
David James1b6e67a2011-05-19 21:32:38 -0700148 'http://git.chromium.org']
149 cros_build_lib.RunCommand(git_ssh_config_cmd, cwd=cwd)
150 cros_build_lib.RunCommand(['git', 'remote', 'update'], cwd=cwd)
151 cros_build_lib.RunCommand(['repo', 'start', prebuilt_branch, '.'], cwd=cwd)
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)
Chris Sosac13bba52011-05-24 15:14:09 -0700160 cros_build_lib.GitPushWithRetry(prebuilt_branch, cwd=cwd)
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
172def LoadPrivateFilters(build_path):
173 """Load private filters based on ebuilds found under _PRIVATE_OVERLAY_DIR.
174
175 This function adds filters to the global set _FILTER_PACKAGES.
176 Args:
177 build_path: Path that _PRIVATE_OVERLAY_DIR is in.
178 """
179 # TODO(scottz): eventually use manifest.xml to find the proper
180 # private overlay path.
181 filter_path = os.path.join(build_path, _PRIVATE_OVERLAY_DIR)
182 files = cros_build_lib.ListFiles(filter_path)
183 filters = []
184 for file in files:
185 if file.endswith('.ebuild'):
186 basename = os.path.basename(file)
187 match = re.match('(.*?)-\d.*.ebuild', basename)
188 if match:
189 filters.append(match.group(1))
190
191 if not filters:
192 raise FiltersEmpty('No filters were returned')
193
194 _FILTER_PACKAGES.update(filters)
195
196
197def ShouldFilterPackage(file_path):
198 """Skip a particular file if it matches a pattern.
199
200 Skip any files that machine the list of packages to filter in
201 _FILTER_PACKAGES.
202
203 Args:
204 file_path: string of a file path to inspect against _FILTER_PACKAGES
205
206 Returns:
207 True if we should filter the package,
208 False otherwise.
209 """
210 for name in _FILTER_PACKAGES:
211 if name in file_path:
212 print 'FILTERING %s' % file_path
213 return True
214
215 return False
216
217
Peter Mayo193f68f2011-04-19 19:08:21 -0400218def _RetryRun(cmd, print_cmd=True, cwd=None):
David James8c846492011-01-25 17:07:29 -0800219 """Run the specified command, retrying if necessary.
220
221 Args:
222 cmd: The command to run.
223 print_cmd: Whether to print out the cmd.
224 shell: Whether to treat the command as a shell.
225 cwd: Working directory to run command in.
226
227 Returns:
228 True if the command succeeded. Otherwise, returns False.
229 """
230
231 # TODO(scottz): port to use _Run or similar when it is available in
232 # cros_build_lib.
233 for attempt in range(_RETRIES):
234 try:
Peter Mayo193f68f2011-04-19 19:08:21 -0400235 output = cros_build_lib.RunCommand(cmd, print_cmd=print_cmd,
David James8c846492011-01-25 17:07:29 -0800236 cwd=cwd)
237 return True
238 except cros_build_lib.RunCommandError:
Peter Mayo193f68f2011-04-19 19:08:21 -0400239 print 'Failed to run %r' % cmd
David James8c846492011-01-25 17:07:29 -0800240 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400241 print 'Retry failed run %r, giving up' % cmd
David James8c846492011-01-25 17:07:29 -0800242 return False
243
244
245def _GsUpload(args):
246 """Upload to GS bucket.
247
248 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800249 args: a tuple of three arguments that contains local_file, remote_file, and
250 the acl used for uploading the file.
David James8c846492011-01-25 17:07:29 -0800251
252 Returns:
253 Return the arg tuple of two if the upload failed
254 """
David Jamesfd0b0852011-02-23 11:15:36 -0800255 (local_file, remote_file, acl) = args
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700256 CANNED_ACLS = ['public-read', 'private', 'bucket-owner-read',
257 'authenticated-read', 'bucket-owner-full-control',
258 'public-read-write']
259 acl_cmd = None
260 if acl in CANNED_ACLS:
Peter Mayo193f68f2011-04-19 19:08:21 -0400261 cmd = [_GSUTIL_BIN, 'cp', '-a', acl, local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700262 else:
263 # For private uploads we assume that the overlay board is set up properly
264 # and a googlestore_acl.xml is present, if not this script errors
Peter Mayo193f68f2011-04-19 19:08:21 -0400265 cmd = [_GSUTIL_BIN, 'cp', '-a', 'private', local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700266 if not os.path.exists(acl):
267 print >> sys.stderr, ('You are specifying either a file that does not '
268 'exist or an unknown canned acl: %s. Aborting '
269 'upload') % acl
270 # emulate the failing of an upload since we are not uploading the file
271 return (local_file, remote_file)
David James8c846492011-01-25 17:07:29 -0800272
Peter Mayo193f68f2011-04-19 19:08:21 -0400273 acl_cmd = [_GSUTIL_BIN, 'setacl', acl, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700274
Peter Mayo193f68f2011-04-19 19:08:21 -0400275 if not _RetryRun(cmd, print_cmd=False):
David James8c846492011-01-25 17:07:29 -0800276 return (local_file, remote_file)
277
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700278 if acl_cmd:
279 # Apply the passed in ACL xml file to the uploaded object.
Peter Mayo193f68f2011-04-19 19:08:21 -0400280 _RetryRun(acl_cmd, print_cmd=False)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700281
282
David Jamesfd0b0852011-02-23 11:15:36 -0800283def RemoteUpload(acl, files, pool=10):
David James8c846492011-01-25 17:07:29 -0800284 """Upload to google storage.
285
286 Create a pool of process and call _GsUpload with the proper arguments.
287
288 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800289 acl: The canned acl used for uploading. acl can be one of: "public-read",
290 "public-read-write", "authenticated-read", "bucket-owner-read",
291 "bucket-owner-full-control", or "private".
David James8c846492011-01-25 17:07:29 -0800292 files: dictionary with keys to local files and values to remote path.
293 pool: integer of maximum proesses to have at the same time.
294
295 Returns:
296 Return a set of tuple arguments of the failed uploads
297 """
298 # TODO(scottz) port this to use _RunManyParallel when it is available in
299 # cros_build_lib
300 pool = multiprocessing.Pool(processes=pool)
301 workers = []
302 for local_file, remote_path in files.iteritems():
David Jamesfd0b0852011-02-23 11:15:36 -0800303 workers.append((local_file, remote_path, acl))
David James8c846492011-01-25 17:07:29 -0800304
305 result = pool.map_async(_GsUpload, workers, chunksize=1)
306 while True:
307 try:
Chris Sosa471532a2011-02-01 15:10:06 -0800308 return set(result.get(60 * 60))
David James8c846492011-01-25 17:07:29 -0800309 except multiprocessing.TimeoutError:
310 pass
311
312
313def GenerateUploadDict(base_local_path, base_remote_path, pkgs):
314 """Build a dictionary of local remote file key pairs to upload.
315
316 Args:
317 base_local_path: The base path to the files on the local hard drive.
318 remote_path: The base path to the remote paths.
319 pkgs: The packages to upload.
320
321 Returns:
322 Returns a dictionary of local_path/remote_path pairs
323 """
324 upload_files = {}
325 for pkg in pkgs:
326 suffix = pkg['CPV'] + '.tbz2'
327 local_path = os.path.join(base_local_path, suffix)
328 assert os.path.exists(local_path)
329 remote_path = '%s/%s' % (base_remote_path.rstrip('/'), suffix)
330 upload_files[local_path] = remote_path
331
332 return upload_files
333
334def GetBoardPathFromCrosOverlayList(build_path, target):
335 """Use the cros_overlay_list to determine the path to the board overlay
336 Args:
337 build_path: The path to the root of the build directory
338 target: The target that we are looking for, could consist of board and
339 board_variant, we handle that properly
340 Returns:
341 The last line from cros_overlay_list as a string
342 """
Chris Sosa471532a2011-02-01 15:10:06 -0800343 script_dir = os.path.join(build_path, 'src/platform/dev/host')
David James8c846492011-01-25 17:07:29 -0800344 cmd = ['./cros_overlay_list']
345 if re.match('.*?_.*', target):
346 (board, variant) = target.split('_')
347 cmd += ['--board', board, '--variant', variant]
348 elif re.match('.*?-\w+', target):
349 cmd += ['--board', target]
350 else:
351 raise UnknownBoardFormat('Unknown format: %s' % target)
352
353 cmd_output = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
354 cwd=script_dir)
355 # We only care about the last entry
356 return cmd_output.output.splitlines().pop()
357
358
359def DeterminePrebuiltConfFile(build_path, target):
360 """Determine the prebuilt.conf file that needs to be updated for prebuilts.
361
362 Args:
363 build_path: The path to the root of the build directory
364 target: String representation of the board. This includes host and board
365 targets
366
367 Returns
368 A string path to a prebuilt.conf file to be updated.
369 """
370 if _HOST_TARGET == target:
371 # We are host.
372 # Without more examples of hosts this is a kludge for now.
373 # TODO(Scottz): as new host targets come online expand this to
374 # work more like boards.
Chris Sosa471532a2011-02-01 15:10:06 -0800375 make_path = _PREBUILT_MAKE_CONF[target]
David James8c846492011-01-25 17:07:29 -0800376 else:
377 # We are a board
378 board = GetBoardPathFromCrosOverlayList(build_path, target)
379 make_path = os.path.join(board, 'prebuilt.conf')
380
381 return make_path
382
383
384def UpdateBinhostConfFile(path, key, value):
385 """Update binhost config file file with key=value.
386
387 Args:
388 path: Filename to update.
389 key: Key to update.
390 value: New value for key.
391 """
392 cwd = os.path.dirname(os.path.abspath(path))
393 filename = os.path.basename(path)
394 if not os.path.isdir(cwd):
395 os.makedirs(cwd)
396 if not os.path.isfile(path):
397 config_file = file(path, 'w')
David James8c846492011-01-25 17:07:29 -0800398 config_file.close()
399 UpdateLocalFile(path, value, key)
Chris Sosac13bba52011-05-24 15:14:09 -0700400 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800401 description = 'Update %s=%s in %s' % (key, value, filename)
Peter Mayo193f68f2011-04-19 19:08:21 -0400402 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800403
404
David Jamesce093af2011-02-23 15:21:58 -0800405def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800406 """Grab all of the packages files associated with a list of binhost_urls.
407
David James05bcb2b2011-02-09 09:25:47 -0800408 Args:
409 binhost_urls: The URLs for the directories containing the Packages files we
410 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800411
412 Returns:
413 A list of PackageIndex objects.
414 """
415 pkg_indexes = []
416 for url in binhost_urls:
417 pkg_index = GrabRemotePackageIndex(url)
418 if pkg_index:
419 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800420 return pkg_indexes
421
422
David James05bcb2b2011-02-09 09:25:47 -0800423
David Jamesc0f158a2011-02-22 16:07:29 -0800424class PrebuiltUploader(object):
425 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800426
David Jamesfd0b0852011-02-23 11:15:36 -0800427 def __init__(self, upload_location, acl, binhost_base_url, pkg_indexes):
David Jamesc0f158a2011-02-22 16:07:29 -0800428 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800429
David Jamesc0f158a2011-02-22 16:07:29 -0800430 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800431
David Jamesc0f158a2011-02-22 16:07:29 -0800432 Args:
433 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800434 acl: The canned acl used for uploading to Google Storage. acl can be one
435 of: "public-read", "public-read-write", "authenticated-read",
436 "bucket-owner-read", "bucket-owner-full-control", or "private". If
437 we are not uploading to Google Storage, this parameter is unused.
438 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800439 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
440 uploading duplicate files, we just link to the old files.
441 """
442 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800443 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800444 self._binhost_base_url = binhost_base_url
445 self._pkg_indexes = pkg_indexes
David James8c846492011-01-25 17:07:29 -0800446
David Jamesc0f158a2011-02-22 16:07:29 -0800447 def _UploadPrebuilt(self, package_path, url_suffix):
448 """Upload host or board prebuilt files to Google Storage space.
David James8c846492011-01-25 17:07:29 -0800449
David Jamesa3bba142011-05-26 21:24:20 -0700450 This function checks to see if we have any new binaries, and, if so,
451 uploads them.
452
David Jamesc0f158a2011-02-22 16:07:29 -0800453 Args:
454 package_path: The path to the packages dir.
David Jamesce093af2011-02-23 15:21:58 -0800455 url_suffix: The remote subdirectory where we should upload the packages.
David Jamesa3bba142011-05-26 21:24:20 -0700456
457 Returns:
458 True if any prebuilts were uploaded.
459 False otherwise.
David Jamesc0f158a2011-02-22 16:07:29 -0800460 """
David James8c846492011-01-25 17:07:29 -0800461
David Jamesc0f158a2011-02-22 16:07:29 -0800462 # Process Packages file, removing duplicates and filtered packages.
463 pkg_index = GrabLocalPackageIndex(package_path)
464 pkg_index.SetUploadLocation(self._binhost_base_url, url_suffix)
465 pkg_index.RemoveFilteredPackages(ShouldFilterPackage)
466 uploads = pkg_index.ResolveDuplicateUploads(self._pkg_indexes)
David James05bcb2b2011-02-09 09:25:47 -0800467
David Jamesa3bba142011-05-26 21:24:20 -0700468 if not uploads:
469 return False
470
David Jamesc0f158a2011-02-22 16:07:29 -0800471 # Write Packages file.
472 tmp_packages_file = pkg_index.WriteToNamedTemporaryFile()
David James05bcb2b2011-02-09 09:25:47 -0800473
David Jamesc0f158a2011-02-22 16:07:29 -0800474 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
475 if remote_location.startswith('gs://'):
476 # Build list of files to upload.
477 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
478 remote_file = '%s/Packages' % remote_location.rstrip('/')
479 upload_files[tmp_packages_file.name] = remote_file
David James05bcb2b2011-02-09 09:25:47 -0800480
David Jamesfd0b0852011-02-23 11:15:36 -0800481 failed_uploads = RemoteUpload(self._acl, upload_files)
David Jamesc0f158a2011-02-22 16:07:29 -0800482 if len(failed_uploads) > 1 or (None not in failed_uploads):
David Jamesf4db1122011-03-17 16:18:05 -0700483 error_msg = ['%s -> %s\n' % args for args in failed_uploads if args]
David Jamesc0f158a2011-02-22 16:07:29 -0800484 raise UploadFailed('Error uploading:\n%s' % error_msg)
485 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400486 pkgs = [p['CPV'] + '.tbz2' for p in uploads]
David Jamesc0f158a2011-02-22 16:07:29 -0800487 ssh_server, remote_path = remote_location.split(':', 1)
Peter Mayo193f68f2011-04-19 19:08:21 -0400488 remote_path = remote_path.rstrip('/')
489 pkg_index = tmp_packages_file.name
490 remote_location = remote_location.rstrip('/')
491 remote_packages = '%s/Packages' % remote_location
492 cmds = [['ssh', ssh_server, 'mkdir', '-p', remote_path],
493 ['rsync', '-av', '--chmod=a+r', pkg_index, remote_packages]]
David Jamesc0f158a2011-02-22 16:07:29 -0800494 if pkgs:
Peter Mayo193f68f2011-04-19 19:08:21 -0400495 cmds.append(['rsync', '-Rav'] + pkgs + [remote_location + '/'])
David Jamesc0f158a2011-02-22 16:07:29 -0800496 for cmd in cmds:
Peter Mayo193f68f2011-04-19 19:08:21 -0400497 if not _RetryRun(cmd, cwd=package_path):
498 raise UploadFailed('Could not run %r' % cmd)
David James8c846492011-01-25 17:07:29 -0800499
David Jamesa3bba142011-05-26 21:24:20 -0700500 return True
501
David James8fa34ea2011-04-15 13:00:20 -0700502 def _UploadBoardTarball(self, board_path, url_suffix):
503 """Upload a tarball of the board at the specified path to Google Storage.
504
505 Args:
506 board_path: The path to the board dir.
507 url_suffix: The remote subdirectory where we should upload the packages.
508 """
509 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
510 assert remote_location.startswith('gs://')
511 cwd, boardname = os.path.split(board_path.rstrip(os.path.sep))
512 tmpdir = tempfile.mkdtemp()
513 try:
514 tarfile = os.path.join(tmpdir, '%s.tbz2' % boardname)
515 cmd = ['sudo', 'tar', '-I', 'pbzip2', '-cf', tarfile]
516 excluded_paths = ('usr/lib/debug', 'usr/local/autotest', 'packages',
517 'tmp')
518 for path in excluded_paths:
519 cmd.append('--exclude=%s/%s/*' % (boardname, path))
520 cmd.append(boardname)
521 cros_build_lib.RunCommand(cmd, cwd=cwd)
522 remote_tarfile = '%s/%s.tbz2' % (remote_location.rstrip('/'), boardname)
523 if _GsUpload((tarfile, remote_tarfile, self._acl)):
524 sys.exit(1)
525 finally:
526 cros_build_lib.RunCommand(['sudo', 'rm', '-rf', tmpdir], cwd=cwd)
527
David Jamesc0f158a2011-02-22 16:07:29 -0800528 def _SyncHostPrebuilts(self, build_path, version, key, git_sync,
529 sync_binhost_conf):
530 """Synchronize host prebuilt files.
David James05bcb2b2011-02-09 09:25:47 -0800531
David Jamesc0f158a2011-02-22 16:07:29 -0800532 This function will sync both the standard host packages, plus the host
533 packages associated with all targets that have been "setup" with the
534 current host's chroot. For instance, if this host has been used to build
535 x86-generic, it will sync the host packages associated with
536 'i686-pc-linux-gnu'. If this host has also been used to build arm-generic,
537 it will also sync the host packages associated with
538 'armv7a-cros-linux-gnueabi'.
David James05bcb2b2011-02-09 09:25:47 -0800539
David Jamesc0f158a2011-02-22 16:07:29 -0800540 Args:
541 build_path: The path to the directory containing the chroot.
542 version: A unique string, intended to be included in the upload path,
543 which identifies the version number of the uploaded prebuilts.
544 key: The variable key to update in the git file.
545 git_sync: If set, update make.conf of target to reference the latest
546 prebuilt packages generated here.
547 sync_binhost_conf: If set, update binhost config file in
548 chromiumos-overlay for the host.
549 """
550 # Upload prebuilts.
551 package_path = os.path.join(build_path, _HOST_PACKAGES_PATH)
552 url_suffix = _REL_HOST_PATH % {'version': version, 'target': _HOST_TARGET}
David James8fa34ea2011-04-15 13:00:20 -0700553 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James05bcb2b2011-02-09 09:25:47 -0800554
David Jamesa3bba142011-05-26 21:24:20 -0700555 if self._UploadPrebuilt(package_path, packages_url_suffix):
556 # Record URL where prebuilts were uploaded.
557 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
558 packages_url_suffix.rstrip('/'))
559 if git_sync:
560 git_file = os.path.join(build_path, _PREBUILT_MAKE_CONF[_HOST_TARGET])
561 RevGitFile(git_file, url_value, key=key)
562 if sync_binhost_conf:
563 binhost_conf = os.path.join(build_path, _BINHOST_CONF_DIR, 'host',
564 '%s-%s.conf' % (_HOST_TARGET, key))
565 UpdateBinhostConfFile(binhost_conf, key, url_value)
David Jamesc0f158a2011-02-22 16:07:29 -0800566
567 def _SyncBoardPrebuilts(self, board, build_path, version, key, git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700568 sync_binhost_conf, upload_board_tarball):
David Jamesc0f158a2011-02-22 16:07:29 -0800569 """Synchronize board prebuilt files.
570
571 Args:
572 board: The board to upload to Google Storage.
573 build_path: The path to the directory containing the chroot.
574 version: A unique string, intended to be included in the upload path,
575 which identifies the version number of the uploaded prebuilts.
576 key: The variable key to update in the git file.
577 git_sync: If set, update make.conf of target to reference the latest
578 prebuilt packages generated here.
579 sync_binhost_conf: If set, update binhost config file in
580 chromiumos-overlay for the current board.
David James8fa34ea2011-04-15 13:00:20 -0700581 upload_board_tarball: Include a tarball of the board in our upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800582 """
David Jamesc0f158a2011-02-22 16:07:29 -0800583 board_path = os.path.join(build_path, _BOARD_PATH % {'board': board})
584 package_path = os.path.join(board_path, 'packages')
585 url_suffix = _REL_BOARD_PATH % {'board': board, 'version': version}
David James8fa34ea2011-04-15 13:00:20 -0700586 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
587
588 # Upload board tarballs in the background.
589 if upload_board_tarball:
590 tar_process = multiprocessing.Process(target=self._UploadBoardTarball,
591 args=(board_path, url_suffix))
592 tar_process.start()
593
594 # Upload prebuilts.
David Jamesa3bba142011-05-26 21:24:20 -0700595 uploaded = self._UploadPrebuilt(package_path, packages_url_suffix)
David James8fa34ea2011-04-15 13:00:20 -0700596
597 # Make sure we finished uploading the board tarballs.
598 if upload_board_tarball:
599 tar_process.join()
600 assert tar_process.exitcode == 0
David Jamesc0f158a2011-02-22 16:07:29 -0800601
David Jamesa3bba142011-05-26 21:24:20 -0700602 if uploaded:
603 # Record URL where prebuilts were uploaded.
604 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
605 packages_url_suffix.rstrip('/'))
606 if git_sync:
607 git_file = DeterminePrebuiltConfFile(build_path, board)
608 RevGitFile(git_file, url_value, key=key)
609 if sync_binhost_conf:
610 binhost_conf = os.path.join(build_path, _BINHOST_CONF_DIR, 'target',
611 '%s-%s.conf' % (board, key))
612 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800613
614
David James8c846492011-01-25 17:07:29 -0800615def usage(parser, msg):
616 """Display usage message and parser help then exit with 1."""
617 print >> sys.stderr, msg
618 parser.print_help()
619 sys.exit(1)
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')
631 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800632 help='Path to the directory containing the chroot')
David James8c846492011-01-25 17:07:29 -0800633 parser.add_option('-s', '--sync-host', dest='sync_host',
634 default=False, action='store_true',
635 help='Sync host prebuilts')
636 parser.add_option('-g', '--git-sync', dest='git_sync',
637 default=False, action='store_true',
638 help='Enable git version sync (This commits to a repo)')
639 parser.add_option('-u', '--upload', dest='upload',
640 default=None,
641 help='Upload location')
642 parser.add_option('-V', '--prepend-version', dest='prepend_version',
643 default=None,
644 help='Add an identifier to the front of the version')
645 parser.add_option('-f', '--filters', dest='filters', action='store_true',
646 default=False,
647 help='Turn on filtering of private ebuild packages')
648 parser.add_option('-k', '--key', dest='key',
649 default='PORTAGE_BINHOST',
650 help='Key to update in make.conf / binhost.conf')
651 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
652 default=False, action='store_true',
653 help='Update binhost.conf')
David Jamesfd0b0852011-02-23 11:15:36 -0800654 parser.add_option('-P', '--private', dest='private', action='store_true',
655 default=False, help='Mark gs:// uploads as private.')
David James8fa34ea2011-04-15 13:00:20 -0700656 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
657 action='store_true', default=False,
658 help='Upload board tarball to Google Storage.')
David James8c846492011-01-25 17:07:29 -0800659
660 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800661 if not options.build_path:
662 usage(parser, 'Error: you need provide a chroot path')
David James8c846492011-01-25 17:07:29 -0800663 if not options.upload:
664 usage(parser, 'Error: you need to provide an upload location using -u')
David James9417f272011-05-26 13:24:47 -0700665 if args:
666 usage(parser, 'Error: invalid arguments passed to prebuilt.py: %r' % args)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700667
David James8fa34ea2011-04-15 13:00:20 -0700668
669 if options.upload_board_tarball and not options.upload.startswith('gs://'):
670 usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
671 '--upload must be a gs:// URL.')
672
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700673 if options.private:
674 if options.sync_host:
675 usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
676 'together, we do not support private host prebuilts')
677
678 if not options.upload.startswith('gs://'):
679 usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
680 '--upload must be a gs:// URL.')
681
682 if options.binhost_base_url != _BINHOST_BASE_URL:
683 usage(parser, 'Error: when using --private the --binhost-base-url '
684 'is automatically derived.')
David Jamesc0f158a2011-02-22 16:07:29 -0800685 return options
686
687def main():
688 options = ParseOptions()
689
David James8c846492011-01-25 17:07:29 -0800690 if options.filters:
691 LoadPrivateFilters(options.build_path)
692
David Jamesfd0b0852011-02-23 11:15:36 -0800693
David James05bcb2b2011-02-09 09:25:47 -0800694 # Calculate a list of Packages index files to compare against. Whenever we
695 # upload a package, we check to make sure it's not already stored in one of
696 # the packages files we uploaded. This list of packages files might contain
697 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800698 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800699
David Jamesc0f158a2011-02-22 16:07:29 -0800700 version = GetVersion()
701 if options.prepend_version:
702 version = '%s-%s' % (options.prepend_version, version)
703
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700704 acl = 'public-read'
705 binhost_base_url = options.binhost_base_url
706
707 if options.private:
708 binhost_base_url = options.upload
709 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
710 options.board)
711 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
712
713 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David Jamesc0f158a2011-02-22 16:07:29 -0800714 pkg_indexes)
715
David James8c846492011-01-25 17:07:29 -0800716 if options.sync_host:
David Jamesc0f158a2011-02-22 16:07:29 -0800717 uploader._SyncHostPrebuilts(options.build_path, version, options.key,
718 options.git_sync, options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800719
720 if options.board:
David Jamesc0f158a2011-02-22 16:07:29 -0800721 uploader._SyncBoardPrebuilts(options.board, options.build_path, version,
722 options.key, options.git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700723 options.sync_binhost_conf,
724 options.upload_board_tarball)
David James8c846492011-01-25 17:07:29 -0800725
726if __name__ == '__main__':
727 main()