blob: 8838b50f2e69ec45acdd90529890f84cc2899f3c [file] [log] [blame]
David James8c846492011-01-25 17:07:29 -08001#!/usr/bin/python
2# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
3# 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
20from chromite.lib.binpkg import (GrabLocalPackageIndex, GrabRemotePackageIndex,
21 PackageIndex)
22"""
23This script is used to upload host prebuilts as well as board BINHOSTS.
24
25If the URL starts with 'gs://', we upload using gsutil to Google Storage.
26Otherwise, rsync is used.
27
28After a build is successfully uploaded a file is updated with the proper
29BINHOST version as well as the target board. This file is defined in GIT_FILE
30
31
32To read more about prebuilts/binhost binary packages please refer to:
33http://sites/chromeos/for-team-members/engineering/releng/prebuilt-binaries-for-streamlining-the-build-process
34
35
36Example of uploading prebuilt amd64 host files to Google Storage:
37./prebuilt.py -p /b/cbuild/build -s -u gs://chromeos-prebuilt
38
39Example of uploading x86-dogfood binhosts to Google Storage:
40./prebuilt.py -b x86-dogfood -p /b/cbuild/build/ -u gs://chromeos-prebuilt -g
41
42Example of uploading prebuilt amd64 host files using rsync:
43./prebuilt.py -p /b/cbuild/build -s -u codf30.jail:/tmp
44"""
45
46# as per http://crosbug.com/5855 always filter the below packages
47_FILTER_PACKAGES = set()
48_RETRIES = 3
49_GSUTIL_BIN = '/b/build/third_party/gsutil/gsutil'
50_HOST_PACKAGES_PATH = 'chroot/var/lib/portage/pkgs'
David James05bcb2b2011-02-09 09:25:47 -080051_CATEGORIES_PATH = 'chroot/etc/portage/categories'
David James8c846492011-01-25 17:07:29 -080052_HOST_TARGET = 'amd64'
53_BOARD_PATH = 'chroot/build/%(board)s'
David James8fa34ea2011-04-15 13:00:20 -070054# board/board-target/version/'
55_REL_BOARD_PATH = 'board/%(board)s/%(version)s'
56# host/host-target/version/'
57_REL_HOST_PATH = 'host/%(target)s/%(version)s'
David James8c846492011-01-25 17:07:29 -080058# Private overlays to look at for builds to filter
59# relative to build path
60_PRIVATE_OVERLAY_DIR = 'src/private-overlays'
Scott Zawalskiab1bed32011-03-16 15:24:24 -070061_GOOGLESTORAGE_ACL_FILE = 'googlestorage_acl.xml'
David James8c846492011-01-25 17:07:29 -080062_BINHOST_BASE_URL = 'http://commondatastorage.googleapis.com/chromeos-prebuilt'
63_PREBUILT_BASE_DIR = 'src/third_party/chromiumos-overlay/chromeos/config/'
64# Created in the event of new host targets becoming available
65_PREBUILT_MAKE_CONF = {'amd64': os.path.join(_PREBUILT_BASE_DIR,
66 'make.conf.amd64-host')}
67_BINHOST_CONF_DIR = 'src/third_party/chromiumos-overlay/chromeos/binhost'
68
69
70class FiltersEmpty(Exception):
71 """Raised when filters are used but none are found."""
72 pass
73
74
75class UploadFailed(Exception):
76 """Raised when one of the files uploaded failed."""
77 pass
78
79class UnknownBoardFormat(Exception):
80 """Raised when a function finds an unknown board format."""
81 pass
82
83class GitPushFailed(Exception):
84 """Raised when a git push failed after retry."""
85 pass
86
87
88def UpdateLocalFile(filename, value, key='PORTAGE_BINHOST'):
89 """Update the key in file with the value passed.
90 File format:
91 key="value"
92 Note quotes are added automatically
93
94 Args:
95 filename: Name of file to modify.
96 value: Value to write with the key.
97 key: The variable key to update. (Default: PORTAGE_BINHOST)
98 """
99 if os.path.exists(filename):
100 file_fh = open(filename)
101 else:
102 file_fh = open(filename, 'w+')
103 file_lines = []
104 found = False
105 keyval_str = '%(key)s=%(value)s'
106 for line in file_fh:
107 # Strip newlines from end of line. We already add newlines below.
108 line = line.rstrip("\n")
109
110 if len(line.split('=')) != 2:
111 # Skip any line that doesn't fit key=val.
112 file_lines.append(line)
113 continue
114
115 file_var, file_val = line.split('=')
116 if file_var == key:
117 found = True
118 print 'Updating %s=%s to %s="%s"' % (file_var, file_val, key, value)
119 value = '"%s"' % value
120 file_lines.append(keyval_str % {'key': key, 'value': value})
121 else:
122 file_lines.append(keyval_str % {'key': file_var, 'value': file_val})
123
124 if not found:
125 file_lines.append(keyval_str % {'key': key, 'value': value})
126
127 file_fh.close()
128 # write out new file
129 new_file_fh = open(filename, 'w')
130 new_file_fh.write('\n'.join(file_lines) + '\n')
131 new_file_fh.close()
132
133
134def RevGitPushWithRetry(retries=5):
135 """Repo sync and then push git changes in flight.
136
137 Args:
138 retries: The number of times to retry before giving up, default: 5
139
140 Raises:
141 GitPushFailed if push was unsuccessful after retries
142 """
Chris Sosa471532a2011-02-01 15:10:06 -0800143 for retry in range(1, retries + 1):
David James8c846492011-01-25 17:07:29 -0800144 try:
Peter Mayo193f68f2011-04-19 19:08:21 -0400145 cros_build_lib.RunCommand(['repo', 'sync', '.'])
146 cros_build_lib.RunCommand(['git', 'push'])
David James8c846492011-01-25 17:07:29 -0800147 break
148 except cros_build_lib.RunCommandError:
149 if retry < retries:
150 print 'Error pushing changes trying again (%s/%s)' % (retry, retries)
Chris Sosa471532a2011-02-01 15:10:06 -0800151 time.sleep(5 * retry)
David James8c846492011-01-25 17:07:29 -0800152 else:
153 raise GitPushFailed('Failed to push change after %s retries' % retries)
154
155
156def RevGitFile(filename, value, retries=5, key='PORTAGE_BINHOST'):
157 """Update and push the git file.
158
159 Args:
160 filename: file to modify that is in a git repo already
161 value: string representing the version of the prebuilt that has been
162 uploaded.
163 retries: The number of times to retry before giving up, default: 5
164 key: The variable key to update in the git file.
165 (Default: PORTAGE_BINHOST)
166 """
167 prebuilt_branch = 'prebuilt_branch'
168 old_cwd = os.getcwd()
169 os.chdir(os.path.dirname(filename))
170
Peter Mayo193f68f2011-04-19 19:08:21 -0400171 commit = cros_build_lib.RunCommand(['git', 'rev-parse', 'HEAD'],
David James518d23e2011-02-23 15:39:55 -0800172 redirect_stdout=True).output
Peter Mayo193f68f2011-04-19 19:08:21 -0400173 cros_build_lib.RunCommand(['git', 'remote', 'update'])
174 cros_build_lib.RunCommand(['repo', 'start', prebuilt_branch, '.'])
175 git_ssh_config_cmd = [
176 'git',
177 'config',
178 'url.ssh://git@gitrw.chromium.org:9222.pushinsteadof',
179 'http://git.chromium.org/git' ]
180 cros_build_lib.RunCommand(git_ssh_config_cmd)
David James8c846492011-01-25 17:07:29 -0800181 description = 'Update %s="%s" in %s' % (key, value, filename)
182 print description
183 try:
184 UpdateLocalFile(filename, value, key)
Peter Mayo193f68f2011-04-19 19:08:21 -0400185 cros_build_lib.RunCommand(['git', 'config', 'push.default', 'tracking'])
186 cros_build_lib.RunCommand(['git', 'commit', '-am', description])
David James8c846492011-01-25 17:07:29 -0800187 RevGitPushWithRetry(retries)
188 finally:
Peter Mayo193f68f2011-04-19 19:08:21 -0400189 cros_build_lib.RunCommand(['repo', 'abandon', 'prebuilt_branch', '.'])
190 cros_build_lib.RunCommand(['git', 'checkout', commit])
David James8c846492011-01-25 17:07:29 -0800191 os.chdir(old_cwd)
192
193
194def GetVersion():
195 """Get the version to put in LATEST and update the git version with."""
196 return datetime.datetime.now().strftime('%d.%m.%y.%H%M%S')
197
198
199def LoadPrivateFilters(build_path):
200 """Load private filters based on ebuilds found under _PRIVATE_OVERLAY_DIR.
201
202 This function adds filters to the global set _FILTER_PACKAGES.
203 Args:
204 build_path: Path that _PRIVATE_OVERLAY_DIR is in.
205 """
206 # TODO(scottz): eventually use manifest.xml to find the proper
207 # private overlay path.
208 filter_path = os.path.join(build_path, _PRIVATE_OVERLAY_DIR)
209 files = cros_build_lib.ListFiles(filter_path)
210 filters = []
211 for file in files:
212 if file.endswith('.ebuild'):
213 basename = os.path.basename(file)
214 match = re.match('(.*?)-\d.*.ebuild', basename)
215 if match:
216 filters.append(match.group(1))
217
218 if not filters:
219 raise FiltersEmpty('No filters were returned')
220
221 _FILTER_PACKAGES.update(filters)
222
223
224def ShouldFilterPackage(file_path):
225 """Skip a particular file if it matches a pattern.
226
227 Skip any files that machine the list of packages to filter in
228 _FILTER_PACKAGES.
229
230 Args:
231 file_path: string of a file path to inspect against _FILTER_PACKAGES
232
233 Returns:
234 True if we should filter the package,
235 False otherwise.
236 """
237 for name in _FILTER_PACKAGES:
238 if name in file_path:
239 print 'FILTERING %s' % file_path
240 return True
241
242 return False
243
244
Peter Mayo193f68f2011-04-19 19:08:21 -0400245def _RetryRun(cmd, print_cmd=True, cwd=None):
David James8c846492011-01-25 17:07:29 -0800246 """Run the specified command, retrying if necessary.
247
248 Args:
249 cmd: The command to run.
250 print_cmd: Whether to print out the cmd.
251 shell: Whether to treat the command as a shell.
252 cwd: Working directory to run command in.
253
254 Returns:
255 True if the command succeeded. Otherwise, returns False.
256 """
257
258 # TODO(scottz): port to use _Run or similar when it is available in
259 # cros_build_lib.
260 for attempt in range(_RETRIES):
261 try:
Peter Mayo193f68f2011-04-19 19:08:21 -0400262 output = cros_build_lib.RunCommand(cmd, print_cmd=print_cmd,
David James8c846492011-01-25 17:07:29 -0800263 cwd=cwd)
264 return True
265 except cros_build_lib.RunCommandError:
Peter Mayo193f68f2011-04-19 19:08:21 -0400266 print 'Failed to run %r' % cmd
David James8c846492011-01-25 17:07:29 -0800267 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400268 print 'Retry failed run %r, giving up' % cmd
David James8c846492011-01-25 17:07:29 -0800269 return False
270
271
272def _GsUpload(args):
273 """Upload to GS bucket.
274
275 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800276 args: a tuple of three arguments that contains local_file, remote_file, and
277 the acl used for uploading the file.
David James8c846492011-01-25 17:07:29 -0800278
279 Returns:
280 Return the arg tuple of two if the upload failed
281 """
David Jamesfd0b0852011-02-23 11:15:36 -0800282 (local_file, remote_file, acl) = args
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700283 CANNED_ACLS = ['public-read', 'private', 'bucket-owner-read',
284 'authenticated-read', 'bucket-owner-full-control',
285 'public-read-write']
286 acl_cmd = None
287 if acl in CANNED_ACLS:
Peter Mayo193f68f2011-04-19 19:08:21 -0400288 cmd = [_GSUTIL_BIN, 'cp', '-a', acl, local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700289 else:
290 # For private uploads we assume that the overlay board is set up properly
291 # and a googlestore_acl.xml is present, if not this script errors
Peter Mayo193f68f2011-04-19 19:08:21 -0400292 cmd = [_GSUTIL_BIN, 'cp', '-a', 'private', local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700293 if not os.path.exists(acl):
294 print >> sys.stderr, ('You are specifying either a file that does not '
295 'exist or an unknown canned acl: %s. Aborting '
296 'upload') % acl
297 # emulate the failing of an upload since we are not uploading the file
298 return (local_file, remote_file)
David James8c846492011-01-25 17:07:29 -0800299
Peter Mayo193f68f2011-04-19 19:08:21 -0400300 acl_cmd = [_GSUTIL_BIN, 'setacl', acl, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700301
Peter Mayo193f68f2011-04-19 19:08:21 -0400302 if not _RetryRun(cmd, print_cmd=False):
David James8c846492011-01-25 17:07:29 -0800303 return (local_file, remote_file)
304
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700305 if acl_cmd:
306 # Apply the passed in ACL xml file to the uploaded object.
Peter Mayo193f68f2011-04-19 19:08:21 -0400307 _RetryRun(acl_cmd, print_cmd=False)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700308
309
David Jamesfd0b0852011-02-23 11:15:36 -0800310def RemoteUpload(acl, files, pool=10):
David James8c846492011-01-25 17:07:29 -0800311 """Upload to google storage.
312
313 Create a pool of process and call _GsUpload with the proper arguments.
314
315 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800316 acl: The canned acl used for uploading. acl can be one of: "public-read",
317 "public-read-write", "authenticated-read", "bucket-owner-read",
318 "bucket-owner-full-control", or "private".
David James8c846492011-01-25 17:07:29 -0800319 files: dictionary with keys to local files and values to remote path.
320 pool: integer of maximum proesses to have at the same time.
321
322 Returns:
323 Return a set of tuple arguments of the failed uploads
324 """
325 # TODO(scottz) port this to use _RunManyParallel when it is available in
326 # cros_build_lib
327 pool = multiprocessing.Pool(processes=pool)
328 workers = []
329 for local_file, remote_path in files.iteritems():
David Jamesfd0b0852011-02-23 11:15:36 -0800330 workers.append((local_file, remote_path, acl))
David James8c846492011-01-25 17:07:29 -0800331
332 result = pool.map_async(_GsUpload, workers, chunksize=1)
333 while True:
334 try:
Chris Sosa471532a2011-02-01 15:10:06 -0800335 return set(result.get(60 * 60))
David James8c846492011-01-25 17:07:29 -0800336 except multiprocessing.TimeoutError:
337 pass
338
339
340def GenerateUploadDict(base_local_path, base_remote_path, pkgs):
341 """Build a dictionary of local remote file key pairs to upload.
342
343 Args:
344 base_local_path: The base path to the files on the local hard drive.
345 remote_path: The base path to the remote paths.
346 pkgs: The packages to upload.
347
348 Returns:
349 Returns a dictionary of local_path/remote_path pairs
350 """
351 upload_files = {}
352 for pkg in pkgs:
353 suffix = pkg['CPV'] + '.tbz2'
354 local_path = os.path.join(base_local_path, suffix)
355 assert os.path.exists(local_path)
356 remote_path = '%s/%s' % (base_remote_path.rstrip('/'), suffix)
357 upload_files[local_path] = remote_path
358
359 return upload_files
360
361def GetBoardPathFromCrosOverlayList(build_path, target):
362 """Use the cros_overlay_list to determine the path to the board overlay
363 Args:
364 build_path: The path to the root of the build directory
365 target: The target that we are looking for, could consist of board and
366 board_variant, we handle that properly
367 Returns:
368 The last line from cros_overlay_list as a string
369 """
Chris Sosa471532a2011-02-01 15:10:06 -0800370 script_dir = os.path.join(build_path, 'src/platform/dev/host')
David James8c846492011-01-25 17:07:29 -0800371 cmd = ['./cros_overlay_list']
372 if re.match('.*?_.*', target):
373 (board, variant) = target.split('_')
374 cmd += ['--board', board, '--variant', variant]
375 elif re.match('.*?-\w+', target):
376 cmd += ['--board', target]
377 else:
378 raise UnknownBoardFormat('Unknown format: %s' % target)
379
380 cmd_output = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
381 cwd=script_dir)
382 # We only care about the last entry
383 return cmd_output.output.splitlines().pop()
384
385
386def DeterminePrebuiltConfFile(build_path, target):
387 """Determine the prebuilt.conf file that needs to be updated for prebuilts.
388
389 Args:
390 build_path: The path to the root of the build directory
391 target: String representation of the board. This includes host and board
392 targets
393
394 Returns
395 A string path to a prebuilt.conf file to be updated.
396 """
397 if _HOST_TARGET == target:
398 # We are host.
399 # Without more examples of hosts this is a kludge for now.
400 # TODO(Scottz): as new host targets come online expand this to
401 # work more like boards.
Chris Sosa471532a2011-02-01 15:10:06 -0800402 make_path = _PREBUILT_MAKE_CONF[target]
David James8c846492011-01-25 17:07:29 -0800403 else:
404 # We are a board
405 board = GetBoardPathFromCrosOverlayList(build_path, target)
406 make_path = os.path.join(board, 'prebuilt.conf')
407
408 return make_path
409
410
411def UpdateBinhostConfFile(path, key, value):
412 """Update binhost config file file with key=value.
413
414 Args:
415 path: Filename to update.
416 key: Key to update.
417 value: New value for key.
418 """
419 cwd = os.path.dirname(os.path.abspath(path))
420 filename = os.path.basename(path)
421 if not os.path.isdir(cwd):
422 os.makedirs(cwd)
423 if not os.path.isfile(path):
424 config_file = file(path, 'w')
David James8c846492011-01-25 17:07:29 -0800425 config_file.close()
426 UpdateLocalFile(path, value, key)
Peter Mayo193f68f2011-04-19 19:08:21 -0400427 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800428 description = 'Update %s=%s in %s' % (key, value, filename)
Peter Mayo193f68f2011-04-19 19:08:21 -0400429 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800430
431
David Jamesce093af2011-02-23 15:21:58 -0800432def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800433 """Grab all of the packages files associated with a list of binhost_urls.
434
David James05bcb2b2011-02-09 09:25:47 -0800435 Args:
436 binhost_urls: The URLs for the directories containing the Packages files we
437 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800438
439 Returns:
440 A list of PackageIndex objects.
441 """
442 pkg_indexes = []
443 for url in binhost_urls:
444 pkg_index = GrabRemotePackageIndex(url)
445 if pkg_index:
446 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800447 return pkg_indexes
448
449
David James05bcb2b2011-02-09 09:25:47 -0800450
David Jamesc0f158a2011-02-22 16:07:29 -0800451class PrebuiltUploader(object):
452 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800453
David Jamesfd0b0852011-02-23 11:15:36 -0800454 def __init__(self, upload_location, acl, binhost_base_url, pkg_indexes):
David Jamesc0f158a2011-02-22 16:07:29 -0800455 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800456
David Jamesc0f158a2011-02-22 16:07:29 -0800457 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800458
David Jamesc0f158a2011-02-22 16:07:29 -0800459 Args:
460 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800461 acl: The canned acl used for uploading to Google Storage. acl can be one
462 of: "public-read", "public-read-write", "authenticated-read",
463 "bucket-owner-read", "bucket-owner-full-control", or "private". If
464 we are not uploading to Google Storage, this parameter is unused.
465 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800466 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
467 uploading duplicate files, we just link to the old files.
468 """
469 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800470 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800471 self._binhost_base_url = binhost_base_url
472 self._pkg_indexes = pkg_indexes
David James8c846492011-01-25 17:07:29 -0800473
David Jamesc0f158a2011-02-22 16:07:29 -0800474 def _UploadPrebuilt(self, package_path, url_suffix):
475 """Upload host or board prebuilt files to Google Storage space.
David James8c846492011-01-25 17:07:29 -0800476
David Jamesc0f158a2011-02-22 16:07:29 -0800477 Args:
478 package_path: The path to the packages dir.
David Jamesce093af2011-02-23 15:21:58 -0800479 url_suffix: The remote subdirectory where we should upload the packages.
David Jamesc0f158a2011-02-22 16:07:29 -0800480 """
David James8c846492011-01-25 17:07:29 -0800481
David Jamesc0f158a2011-02-22 16:07:29 -0800482 # Process Packages file, removing duplicates and filtered packages.
483 pkg_index = GrabLocalPackageIndex(package_path)
484 pkg_index.SetUploadLocation(self._binhost_base_url, url_suffix)
485 pkg_index.RemoveFilteredPackages(ShouldFilterPackage)
486 uploads = pkg_index.ResolveDuplicateUploads(self._pkg_indexes)
David James05bcb2b2011-02-09 09:25:47 -0800487
David Jamesc0f158a2011-02-22 16:07:29 -0800488 # Write Packages file.
489 tmp_packages_file = pkg_index.WriteToNamedTemporaryFile()
David James05bcb2b2011-02-09 09:25:47 -0800490
David Jamesc0f158a2011-02-22 16:07:29 -0800491 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
492 if remote_location.startswith('gs://'):
493 # Build list of files to upload.
494 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
495 remote_file = '%s/Packages' % remote_location.rstrip('/')
496 upload_files[tmp_packages_file.name] = remote_file
David James05bcb2b2011-02-09 09:25:47 -0800497
David Jamesfd0b0852011-02-23 11:15:36 -0800498 failed_uploads = RemoteUpload(self._acl, upload_files)
David Jamesc0f158a2011-02-22 16:07:29 -0800499 if len(failed_uploads) > 1 or (None not in failed_uploads):
David Jamesf4db1122011-03-17 16:18:05 -0700500 error_msg = ['%s -> %s\n' % args for args in failed_uploads if args]
David Jamesc0f158a2011-02-22 16:07:29 -0800501 raise UploadFailed('Error uploading:\n%s' % error_msg)
502 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400503 pkgs = [p['CPV'] + '.tbz2' for p in uploads]
David Jamesc0f158a2011-02-22 16:07:29 -0800504 ssh_server, remote_path = remote_location.split(':', 1)
Peter Mayo193f68f2011-04-19 19:08:21 -0400505 remote_path = remote_path.rstrip('/')
506 pkg_index = tmp_packages_file.name
507 remote_location = remote_location.rstrip('/')
508 remote_packages = '%s/Packages' % remote_location
509 cmds = [['ssh', ssh_server, 'mkdir', '-p', remote_path],
510 ['rsync', '-av', '--chmod=a+r', pkg_index, remote_packages]]
David Jamesc0f158a2011-02-22 16:07:29 -0800511 if pkgs:
Peter Mayo193f68f2011-04-19 19:08:21 -0400512 cmds.append(['rsync', '-Rav'] + pkgs + [remote_location + '/'])
David Jamesc0f158a2011-02-22 16:07:29 -0800513 for cmd in cmds:
Peter Mayo193f68f2011-04-19 19:08:21 -0400514 if not _RetryRun(cmd, cwd=package_path):
515 raise UploadFailed('Could not run %r' % cmd)
David James8c846492011-01-25 17:07:29 -0800516
David James8fa34ea2011-04-15 13:00:20 -0700517 def _UploadBoardTarball(self, board_path, url_suffix):
518 """Upload a tarball of the board at the specified path to Google Storage.
519
520 Args:
521 board_path: The path to the board dir.
522 url_suffix: The remote subdirectory where we should upload the packages.
523 """
524 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
525 assert remote_location.startswith('gs://')
526 cwd, boardname = os.path.split(board_path.rstrip(os.path.sep))
527 tmpdir = tempfile.mkdtemp()
528 try:
529 tarfile = os.path.join(tmpdir, '%s.tbz2' % boardname)
530 cmd = ['sudo', 'tar', '-I', 'pbzip2', '-cf', tarfile]
531 excluded_paths = ('usr/lib/debug', 'usr/local/autotest', 'packages',
532 'tmp')
533 for path in excluded_paths:
534 cmd.append('--exclude=%s/%s/*' % (boardname, path))
535 cmd.append(boardname)
536 cros_build_lib.RunCommand(cmd, cwd=cwd)
537 remote_tarfile = '%s/%s.tbz2' % (remote_location.rstrip('/'), boardname)
538 if _GsUpload((tarfile, remote_tarfile, self._acl)):
539 sys.exit(1)
540 finally:
541 cros_build_lib.RunCommand(['sudo', 'rm', '-rf', tmpdir], cwd=cwd)
542
David Jamesc0f158a2011-02-22 16:07:29 -0800543 def _SyncHostPrebuilts(self, build_path, version, key, git_sync,
544 sync_binhost_conf):
545 """Synchronize host prebuilt files.
David James05bcb2b2011-02-09 09:25:47 -0800546
David Jamesc0f158a2011-02-22 16:07:29 -0800547 This function will sync both the standard host packages, plus the host
548 packages associated with all targets that have been "setup" with the
549 current host's chroot. For instance, if this host has been used to build
550 x86-generic, it will sync the host packages associated with
551 'i686-pc-linux-gnu'. If this host has also been used to build arm-generic,
552 it will also sync the host packages associated with
553 'armv7a-cros-linux-gnueabi'.
David James05bcb2b2011-02-09 09:25:47 -0800554
David Jamesc0f158a2011-02-22 16:07:29 -0800555 Args:
556 build_path: The path to the directory containing the chroot.
557 version: A unique string, intended to be included in the upload path,
558 which identifies the version number of the uploaded prebuilts.
559 key: The variable key to update in the git file.
560 git_sync: If set, update make.conf of target to reference the latest
561 prebuilt packages generated here.
562 sync_binhost_conf: If set, update binhost config file in
563 chromiumos-overlay for the host.
564 """
565 # Upload prebuilts.
566 package_path = os.path.join(build_path, _HOST_PACKAGES_PATH)
567 url_suffix = _REL_HOST_PATH % {'version': version, 'target': _HOST_TARGET}
David James8fa34ea2011-04-15 13:00:20 -0700568 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
569 self._UploadPrebuilt(package_path, packages_url_suffix)
David James05bcb2b2011-02-09 09:25:47 -0800570
David Jamesc0f158a2011-02-22 16:07:29 -0800571 # Record URL where prebuilts were uploaded.
572 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
David Jamesf0e6fd72011-04-15 15:58:07 -0700573 packages_url_suffix.rstrip('/'))
David Jamesc0f158a2011-02-22 16:07:29 -0800574 if git_sync:
575 git_file = os.path.join(build_path, _PREBUILT_MAKE_CONF[_HOST_TARGET])
576 RevGitFile(git_file, url_value, key=key)
577 if sync_binhost_conf:
578 binhost_conf = os.path.join(build_path, _BINHOST_CONF_DIR, 'host',
579 '%s-%s.conf' % (_HOST_TARGET, key))
580 UpdateBinhostConfFile(binhost_conf, key, url_value)
581
582 def _SyncBoardPrebuilts(self, board, build_path, version, key, git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700583 sync_binhost_conf, upload_board_tarball):
David Jamesc0f158a2011-02-22 16:07:29 -0800584 """Synchronize board prebuilt files.
585
586 Args:
587 board: The board to upload to Google Storage.
588 build_path: The path to the directory containing the chroot.
589 version: A unique string, intended to be included in the upload path,
590 which identifies the version number of the uploaded prebuilts.
591 key: The variable key to update in the git file.
592 git_sync: If set, update make.conf of target to reference the latest
593 prebuilt packages generated here.
594 sync_binhost_conf: If set, update binhost config file in
595 chromiumos-overlay for the current board.
David James8fa34ea2011-04-15 13:00:20 -0700596 upload_board_tarball: Include a tarball of the board in our upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800597 """
David Jamesc0f158a2011-02-22 16:07:29 -0800598 board_path = os.path.join(build_path, _BOARD_PATH % {'board': board})
599 package_path = os.path.join(board_path, 'packages')
600 url_suffix = _REL_BOARD_PATH % {'board': board, 'version': version}
David James8fa34ea2011-04-15 13:00:20 -0700601 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
602
603 # Upload board tarballs in the background.
604 if upload_board_tarball:
605 tar_process = multiprocessing.Process(target=self._UploadBoardTarball,
606 args=(board_path, url_suffix))
607 tar_process.start()
608
609 # Upload prebuilts.
610 self._UploadPrebuilt(package_path, packages_url_suffix)
611
612 # Make sure we finished uploading the board tarballs.
613 if upload_board_tarball:
614 tar_process.join()
615 assert tar_process.exitcode == 0
David Jamesc0f158a2011-02-22 16:07:29 -0800616
617 # Record URL where prebuilts were uploaded.
618 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
David Jamesf0e6fd72011-04-15 15:58:07 -0700619 packages_url_suffix.rstrip('/'))
David Jamesc0f158a2011-02-22 16:07:29 -0800620 if git_sync:
621 git_file = DeterminePrebuiltConfFile(build_path, board)
622 RevGitFile(git_file, url_value, key=key)
623 if sync_binhost_conf:
624 binhost_conf = os.path.join(build_path, _BINHOST_CONF_DIR, 'target',
625 '%s-%s.conf' % (board, key))
626 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800627
628
David James8c846492011-01-25 17:07:29 -0800629def usage(parser, msg):
630 """Display usage message and parser help then exit with 1."""
631 print >> sys.stderr, msg
632 parser.print_help()
633 sys.exit(1)
634
David Jamesc0f158a2011-02-22 16:07:29 -0800635def ParseOptions():
David James8c846492011-01-25 17:07:29 -0800636 parser = optparse.OptionParser()
637 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
638 default=_BINHOST_BASE_URL,
639 help='Base URL to use for binhost in make.conf updates')
640 parser.add_option('', '--previous-binhost-url', action='append',
641 default=[], dest='previous_binhost_url',
642 help='Previous binhost URL')
643 parser.add_option('-b', '--board', dest='board', default=None,
644 help='Board type that was built on this machine')
645 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800646 help='Path to the directory containing the chroot')
David James8c846492011-01-25 17:07:29 -0800647 parser.add_option('-s', '--sync-host', dest='sync_host',
648 default=False, action='store_true',
649 help='Sync host prebuilts')
650 parser.add_option('-g', '--git-sync', dest='git_sync',
651 default=False, action='store_true',
652 help='Enable git version sync (This commits to a repo)')
653 parser.add_option('-u', '--upload', dest='upload',
654 default=None,
655 help='Upload location')
656 parser.add_option('-V', '--prepend-version', dest='prepend_version',
657 default=None,
658 help='Add an identifier to the front of the version')
659 parser.add_option('-f', '--filters', dest='filters', action='store_true',
660 default=False,
661 help='Turn on filtering of private ebuild packages')
662 parser.add_option('-k', '--key', dest='key',
663 default='PORTAGE_BINHOST',
664 help='Key to update in make.conf / binhost.conf')
665 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
666 default=False, action='store_true',
667 help='Update binhost.conf')
David Jamesfd0b0852011-02-23 11:15:36 -0800668 parser.add_option('-P', '--private', dest='private', action='store_true',
669 default=False, help='Mark gs:// uploads as private.')
David James8fa34ea2011-04-15 13:00:20 -0700670 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
671 action='store_true', default=False,
672 help='Upload board tarball to Google Storage.')
David James8c846492011-01-25 17:07:29 -0800673
674 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800675 if not options.build_path:
676 usage(parser, 'Error: you need provide a chroot path')
David James8c846492011-01-25 17:07:29 -0800677 if not options.upload:
678 usage(parser, 'Error: you need to provide an upload location using -u')
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700679
David James8fa34ea2011-04-15 13:00:20 -0700680
681 if options.upload_board_tarball and not options.upload.startswith('gs://'):
682 usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
683 '--upload must be a gs:// URL.')
684
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700685 if options.private:
686 if options.sync_host:
687 usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
688 'together, we do not support private host prebuilts')
689
690 if not options.upload.startswith('gs://'):
691 usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
692 '--upload must be a gs:// URL.')
693
694 if options.binhost_base_url != _BINHOST_BASE_URL:
695 usage(parser, 'Error: when using --private the --binhost-base-url '
696 'is automatically derived.')
David Jamesc0f158a2011-02-22 16:07:29 -0800697 return options
698
699def main():
700 options = ParseOptions()
701
David James8c846492011-01-25 17:07:29 -0800702 if options.filters:
703 LoadPrivateFilters(options.build_path)
704
David Jamesfd0b0852011-02-23 11:15:36 -0800705
David James05bcb2b2011-02-09 09:25:47 -0800706 # Calculate a list of Packages index files to compare against. Whenever we
707 # upload a package, we check to make sure it's not already stored in one of
708 # the packages files we uploaded. This list of packages files might contain
709 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800710 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800711
David Jamesc0f158a2011-02-22 16:07:29 -0800712 version = GetVersion()
713 if options.prepend_version:
714 version = '%s-%s' % (options.prepend_version, version)
715
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700716 acl = 'public-read'
717 binhost_base_url = options.binhost_base_url
718
719 if options.private:
720 binhost_base_url = options.upload
721 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
722 options.board)
723 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
724
725 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David Jamesc0f158a2011-02-22 16:07:29 -0800726 pkg_indexes)
727
David James8c846492011-01-25 17:07:29 -0800728 if options.sync_host:
David Jamesc0f158a2011-02-22 16:07:29 -0800729 uploader._SyncHostPrebuilts(options.build_path, version, options.key,
730 options.git_sync, options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800731
732 if options.board:
David Jamesc0f158a2011-02-22 16:07:29 -0800733 uploader._SyncBoardPrebuilts(options.board, options.build_path, version,
734 options.key, options.git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700735 options.sync_binhost_conf,
736 options.upload_board_tarball)
David James8c846492011-01-25 17:07:29 -0800737
738if __name__ == '__main__':
739 main()