blob: 2945e648d763f278ba6d0ff4ab9b0370784ec2bb [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
David James1b6e67a2011-05-19 21:32:38 -0700134def RevGitPushWithRetry(tracking_branch, cwd, retries=5):
David James8c846492011-01-25 17:07:29 -0800135 """Repo sync and then push git changes in flight.
136
137 Args:
David James1b6e67a2011-05-19 21:32:38 -0700138 tracking_branch: Branch to rebase against.
139 cwd: Directory to push in.
David James8c846492011-01-25 17:07:29 -0800140 retries: The number of times to retry before giving up, default: 5
141
142 Raises:
143 GitPushFailed if push was unsuccessful after retries
144 """
Chris Sosa471532a2011-02-01 15:10:06 -0800145 for retry in range(1, retries + 1):
David James8c846492011-01-25 17:07:29 -0800146 try:
David James1b6e67a2011-05-19 21:32:38 -0700147 cros_build_lib.RunCommand(['git', 'remote', 'update'], cwd=cwd)
148 cros_build_lib.RunCommand(['git', 'rebase', tracking_branch], cwd=cwd)
149 cros_build_lib.RunCommand(['git', 'push'], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800150 break
151 except cros_build_lib.RunCommandError:
152 if retry < retries:
153 print 'Error pushing changes trying again (%s/%s)' % (retry, retries)
Chris Sosa471532a2011-02-01 15:10:06 -0800154 time.sleep(5 * retry)
David James8c846492011-01-25 17:07:29 -0800155 else:
156 raise GitPushFailed('Failed to push change after %s retries' % retries)
157
158
David James1b6e67a2011-05-19 21:32:38 -0700159def _GetTrackingBranch(branch, cwd):
160 """Get the tracking branch for the specified branch / directory.
161
162 Args:
163 branch: Branch to examine for tracking branch.
164 cwd: Directory to look in.
165 """
166 info = {}
167 for key in ('remote', 'merge'):
168 cmd = ['git', 'config', 'branch.%s.%s' % (branch, key)]
169 info[key] = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
170 cwd=cwd).output.strip()
171 assert info["merge"].startswith("refs/heads/")
172 return info["merge"].replace("refs/heads", info["remote"])
173
174
David James8c846492011-01-25 17:07:29 -0800175def RevGitFile(filename, value, retries=5, key='PORTAGE_BINHOST'):
176 """Update and push the git file.
177
178 Args:
179 filename: file to modify that is in a git repo already
180 value: string representing the version of the prebuilt that has been
181 uploaded.
182 retries: The number of times to retry before giving up, default: 5
183 key: The variable key to update in the git file.
184 (Default: PORTAGE_BINHOST)
185 """
186 prebuilt_branch = 'prebuilt_branch'
David James1b6e67a2011-05-19 21:32:38 -0700187 cwd = os.path.abspath(os.path.dirname(filename))
188 commit = cros_build_lib.RunCommand(['git', 'rev-parse', 'HEAD'], cwd=cwd,
Peter Mayofe0e6872011-04-20 00:52:08 -0400189 redirect_stdout=True).output.rstrip()
Peter Mayo193f68f2011-04-19 19:08:21 -0400190 git_ssh_config_cmd = [
191 'git',
192 'config',
David James1b6e67a2011-05-19 21:32:38 -0700193 'url.ssh://gerrit.chromium.org:29418.insteadof',
194 'http://git.chromium.org']
195 cros_build_lib.RunCommand(git_ssh_config_cmd, cwd=cwd)
196 cros_build_lib.RunCommand(['git', 'remote', 'update'], cwd=cwd)
197 cros_build_lib.RunCommand(['repo', 'start', prebuilt_branch, '.'], cwd=cwd)
198 tracking_branch = _GetTrackingBranch(prebuilt_branch, cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800199 description = 'Update %s="%s" in %s' % (key, value, filename)
200 print description
201 try:
202 UpdateLocalFile(filename, value, key)
David James1b6e67a2011-05-19 21:32:38 -0700203 cros_build_lib.RunCommand(['git', 'config', 'push.default', 'tracking'],
204 cwd=cwd)
205 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
206 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
207 RevGitPushWithRetry(tracking_branch, cwd, retries)
David James8c846492011-01-25 17:07:29 -0800208 finally:
David James1b6e67a2011-05-19 21:32:38 -0700209 cros_build_lib.RunCommand(['git', 'checkout', commit], cwd=cwd)
210 cros_build_lib.RunCommand(['repo', 'abandon', 'prebuilt_branch', '.'],
211 cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800212
213
214def GetVersion():
215 """Get the version to put in LATEST and update the git version with."""
216 return datetime.datetime.now().strftime('%d.%m.%y.%H%M%S')
217
218
219def LoadPrivateFilters(build_path):
220 """Load private filters based on ebuilds found under _PRIVATE_OVERLAY_DIR.
221
222 This function adds filters to the global set _FILTER_PACKAGES.
223 Args:
224 build_path: Path that _PRIVATE_OVERLAY_DIR is in.
225 """
226 # TODO(scottz): eventually use manifest.xml to find the proper
227 # private overlay path.
228 filter_path = os.path.join(build_path, _PRIVATE_OVERLAY_DIR)
229 files = cros_build_lib.ListFiles(filter_path)
230 filters = []
231 for file in files:
232 if file.endswith('.ebuild'):
233 basename = os.path.basename(file)
234 match = re.match('(.*?)-\d.*.ebuild', basename)
235 if match:
236 filters.append(match.group(1))
237
238 if not filters:
239 raise FiltersEmpty('No filters were returned')
240
241 _FILTER_PACKAGES.update(filters)
242
243
244def ShouldFilterPackage(file_path):
245 """Skip a particular file if it matches a pattern.
246
247 Skip any files that machine the list of packages to filter in
248 _FILTER_PACKAGES.
249
250 Args:
251 file_path: string of a file path to inspect against _FILTER_PACKAGES
252
253 Returns:
254 True if we should filter the package,
255 False otherwise.
256 """
257 for name in _FILTER_PACKAGES:
258 if name in file_path:
259 print 'FILTERING %s' % file_path
260 return True
261
262 return False
263
264
Peter Mayo193f68f2011-04-19 19:08:21 -0400265def _RetryRun(cmd, print_cmd=True, cwd=None):
David James8c846492011-01-25 17:07:29 -0800266 """Run the specified command, retrying if necessary.
267
268 Args:
269 cmd: The command to run.
270 print_cmd: Whether to print out the cmd.
271 shell: Whether to treat the command as a shell.
272 cwd: Working directory to run command in.
273
274 Returns:
275 True if the command succeeded. Otherwise, returns False.
276 """
277
278 # TODO(scottz): port to use _Run or similar when it is available in
279 # cros_build_lib.
280 for attempt in range(_RETRIES):
281 try:
Peter Mayo193f68f2011-04-19 19:08:21 -0400282 output = cros_build_lib.RunCommand(cmd, print_cmd=print_cmd,
David James8c846492011-01-25 17:07:29 -0800283 cwd=cwd)
284 return True
285 except cros_build_lib.RunCommandError:
Peter Mayo193f68f2011-04-19 19:08:21 -0400286 print 'Failed to run %r' % cmd
David James8c846492011-01-25 17:07:29 -0800287 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400288 print 'Retry failed run %r, giving up' % cmd
David James8c846492011-01-25 17:07:29 -0800289 return False
290
291
292def _GsUpload(args):
293 """Upload to GS bucket.
294
295 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800296 args: a tuple of three arguments that contains local_file, remote_file, and
297 the acl used for uploading the file.
David James8c846492011-01-25 17:07:29 -0800298
299 Returns:
300 Return the arg tuple of two if the upload failed
301 """
David Jamesfd0b0852011-02-23 11:15:36 -0800302 (local_file, remote_file, acl) = args
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700303 CANNED_ACLS = ['public-read', 'private', 'bucket-owner-read',
304 'authenticated-read', 'bucket-owner-full-control',
305 'public-read-write']
306 acl_cmd = None
307 if acl in CANNED_ACLS:
Peter Mayo193f68f2011-04-19 19:08:21 -0400308 cmd = [_GSUTIL_BIN, 'cp', '-a', acl, local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700309 else:
310 # For private uploads we assume that the overlay board is set up properly
311 # and a googlestore_acl.xml is present, if not this script errors
Peter Mayo193f68f2011-04-19 19:08:21 -0400312 cmd = [_GSUTIL_BIN, 'cp', '-a', 'private', local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700313 if not os.path.exists(acl):
314 print >> sys.stderr, ('You are specifying either a file that does not '
315 'exist or an unknown canned acl: %s. Aborting '
316 'upload') % acl
317 # emulate the failing of an upload since we are not uploading the file
318 return (local_file, remote_file)
David James8c846492011-01-25 17:07:29 -0800319
Peter Mayo193f68f2011-04-19 19:08:21 -0400320 acl_cmd = [_GSUTIL_BIN, 'setacl', acl, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700321
Peter Mayo193f68f2011-04-19 19:08:21 -0400322 if not _RetryRun(cmd, print_cmd=False):
David James8c846492011-01-25 17:07:29 -0800323 return (local_file, remote_file)
324
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700325 if acl_cmd:
326 # Apply the passed in ACL xml file to the uploaded object.
Peter Mayo193f68f2011-04-19 19:08:21 -0400327 _RetryRun(acl_cmd, print_cmd=False)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700328
329
David Jamesfd0b0852011-02-23 11:15:36 -0800330def RemoteUpload(acl, files, pool=10):
David James8c846492011-01-25 17:07:29 -0800331 """Upload to google storage.
332
333 Create a pool of process and call _GsUpload with the proper arguments.
334
335 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800336 acl: The canned acl used for uploading. acl can be one of: "public-read",
337 "public-read-write", "authenticated-read", "bucket-owner-read",
338 "bucket-owner-full-control", or "private".
David James8c846492011-01-25 17:07:29 -0800339 files: dictionary with keys to local files and values to remote path.
340 pool: integer of maximum proesses to have at the same time.
341
342 Returns:
343 Return a set of tuple arguments of the failed uploads
344 """
345 # TODO(scottz) port this to use _RunManyParallel when it is available in
346 # cros_build_lib
347 pool = multiprocessing.Pool(processes=pool)
348 workers = []
349 for local_file, remote_path in files.iteritems():
David Jamesfd0b0852011-02-23 11:15:36 -0800350 workers.append((local_file, remote_path, acl))
David James8c846492011-01-25 17:07:29 -0800351
352 result = pool.map_async(_GsUpload, workers, chunksize=1)
353 while True:
354 try:
Chris Sosa471532a2011-02-01 15:10:06 -0800355 return set(result.get(60 * 60))
David James8c846492011-01-25 17:07:29 -0800356 except multiprocessing.TimeoutError:
357 pass
358
359
360def GenerateUploadDict(base_local_path, base_remote_path, pkgs):
361 """Build a dictionary of local remote file key pairs to upload.
362
363 Args:
364 base_local_path: The base path to the files on the local hard drive.
365 remote_path: The base path to the remote paths.
366 pkgs: The packages to upload.
367
368 Returns:
369 Returns a dictionary of local_path/remote_path pairs
370 """
371 upload_files = {}
372 for pkg in pkgs:
373 suffix = pkg['CPV'] + '.tbz2'
374 local_path = os.path.join(base_local_path, suffix)
375 assert os.path.exists(local_path)
376 remote_path = '%s/%s' % (base_remote_path.rstrip('/'), suffix)
377 upload_files[local_path] = remote_path
378
379 return upload_files
380
381def GetBoardPathFromCrosOverlayList(build_path, target):
382 """Use the cros_overlay_list to determine the path to the board overlay
383 Args:
384 build_path: The path to the root of the build directory
385 target: The target that we are looking for, could consist of board and
386 board_variant, we handle that properly
387 Returns:
388 The last line from cros_overlay_list as a string
389 """
Chris Sosa471532a2011-02-01 15:10:06 -0800390 script_dir = os.path.join(build_path, 'src/platform/dev/host')
David James8c846492011-01-25 17:07:29 -0800391 cmd = ['./cros_overlay_list']
392 if re.match('.*?_.*', target):
393 (board, variant) = target.split('_')
394 cmd += ['--board', board, '--variant', variant]
395 elif re.match('.*?-\w+', target):
396 cmd += ['--board', target]
397 else:
398 raise UnknownBoardFormat('Unknown format: %s' % target)
399
400 cmd_output = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
401 cwd=script_dir)
402 # We only care about the last entry
403 return cmd_output.output.splitlines().pop()
404
405
406def DeterminePrebuiltConfFile(build_path, target):
407 """Determine the prebuilt.conf file that needs to be updated for prebuilts.
408
409 Args:
410 build_path: The path to the root of the build directory
411 target: String representation of the board. This includes host and board
412 targets
413
414 Returns
415 A string path to a prebuilt.conf file to be updated.
416 """
417 if _HOST_TARGET == target:
418 # We are host.
419 # Without more examples of hosts this is a kludge for now.
420 # TODO(Scottz): as new host targets come online expand this to
421 # work more like boards.
Chris Sosa471532a2011-02-01 15:10:06 -0800422 make_path = _PREBUILT_MAKE_CONF[target]
David James8c846492011-01-25 17:07:29 -0800423 else:
424 # We are a board
425 board = GetBoardPathFromCrosOverlayList(build_path, target)
426 make_path = os.path.join(board, 'prebuilt.conf')
427
428 return make_path
429
430
431def UpdateBinhostConfFile(path, key, value):
432 """Update binhost config file file with key=value.
433
434 Args:
435 path: Filename to update.
436 key: Key to update.
437 value: New value for key.
438 """
439 cwd = os.path.dirname(os.path.abspath(path))
440 filename = os.path.basename(path)
441 if not os.path.isdir(cwd):
442 os.makedirs(cwd)
443 if not os.path.isfile(path):
444 config_file = file(path, 'w')
David James8c846492011-01-25 17:07:29 -0800445 config_file.close()
446 UpdateLocalFile(path, value, key)
Peter Mayo193f68f2011-04-19 19:08:21 -0400447 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800448 description = 'Update %s=%s in %s' % (key, value, filename)
Peter Mayo193f68f2011-04-19 19:08:21 -0400449 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800450
451
David Jamesce093af2011-02-23 15:21:58 -0800452def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800453 """Grab all of the packages files associated with a list of binhost_urls.
454
David James05bcb2b2011-02-09 09:25:47 -0800455 Args:
456 binhost_urls: The URLs for the directories containing the Packages files we
457 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800458
459 Returns:
460 A list of PackageIndex objects.
461 """
462 pkg_indexes = []
463 for url in binhost_urls:
464 pkg_index = GrabRemotePackageIndex(url)
465 if pkg_index:
466 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800467 return pkg_indexes
468
469
David James05bcb2b2011-02-09 09:25:47 -0800470
David Jamesc0f158a2011-02-22 16:07:29 -0800471class PrebuiltUploader(object):
472 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800473
David Jamesfd0b0852011-02-23 11:15:36 -0800474 def __init__(self, upload_location, acl, binhost_base_url, pkg_indexes):
David Jamesc0f158a2011-02-22 16:07:29 -0800475 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800476
David Jamesc0f158a2011-02-22 16:07:29 -0800477 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800478
David Jamesc0f158a2011-02-22 16:07:29 -0800479 Args:
480 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800481 acl: The canned acl used for uploading to Google Storage. acl can be one
482 of: "public-read", "public-read-write", "authenticated-read",
483 "bucket-owner-read", "bucket-owner-full-control", or "private". If
484 we are not uploading to Google Storage, this parameter is unused.
485 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800486 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
487 uploading duplicate files, we just link to the old files.
488 """
489 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800490 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800491 self._binhost_base_url = binhost_base_url
492 self._pkg_indexes = pkg_indexes
David James8c846492011-01-25 17:07:29 -0800493
David Jamesc0f158a2011-02-22 16:07:29 -0800494 def _UploadPrebuilt(self, package_path, url_suffix):
495 """Upload host or board prebuilt files to Google Storage space.
David James8c846492011-01-25 17:07:29 -0800496
David Jamesc0f158a2011-02-22 16:07:29 -0800497 Args:
498 package_path: The path to the packages dir.
David Jamesce093af2011-02-23 15:21:58 -0800499 url_suffix: The remote subdirectory where we should upload the packages.
David Jamesc0f158a2011-02-22 16:07:29 -0800500 """
David James8c846492011-01-25 17:07:29 -0800501
David Jamesc0f158a2011-02-22 16:07:29 -0800502 # Process Packages file, removing duplicates and filtered packages.
503 pkg_index = GrabLocalPackageIndex(package_path)
504 pkg_index.SetUploadLocation(self._binhost_base_url, url_suffix)
505 pkg_index.RemoveFilteredPackages(ShouldFilterPackage)
506 uploads = pkg_index.ResolveDuplicateUploads(self._pkg_indexes)
David James05bcb2b2011-02-09 09:25:47 -0800507
David Jamesc0f158a2011-02-22 16:07:29 -0800508 # Write Packages file.
509 tmp_packages_file = pkg_index.WriteToNamedTemporaryFile()
David James05bcb2b2011-02-09 09:25:47 -0800510
David Jamesc0f158a2011-02-22 16:07:29 -0800511 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
512 if remote_location.startswith('gs://'):
513 # Build list of files to upload.
514 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
515 remote_file = '%s/Packages' % remote_location.rstrip('/')
516 upload_files[tmp_packages_file.name] = remote_file
David James05bcb2b2011-02-09 09:25:47 -0800517
David Jamesfd0b0852011-02-23 11:15:36 -0800518 failed_uploads = RemoteUpload(self._acl, upload_files)
David Jamesc0f158a2011-02-22 16:07:29 -0800519 if len(failed_uploads) > 1 or (None not in failed_uploads):
David Jamesf4db1122011-03-17 16:18:05 -0700520 error_msg = ['%s -> %s\n' % args for args in failed_uploads if args]
David Jamesc0f158a2011-02-22 16:07:29 -0800521 raise UploadFailed('Error uploading:\n%s' % error_msg)
522 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400523 pkgs = [p['CPV'] + '.tbz2' for p in uploads]
David Jamesc0f158a2011-02-22 16:07:29 -0800524 ssh_server, remote_path = remote_location.split(':', 1)
Peter Mayo193f68f2011-04-19 19:08:21 -0400525 remote_path = remote_path.rstrip('/')
526 pkg_index = tmp_packages_file.name
527 remote_location = remote_location.rstrip('/')
528 remote_packages = '%s/Packages' % remote_location
529 cmds = [['ssh', ssh_server, 'mkdir', '-p', remote_path],
530 ['rsync', '-av', '--chmod=a+r', pkg_index, remote_packages]]
David Jamesc0f158a2011-02-22 16:07:29 -0800531 if pkgs:
Peter Mayo193f68f2011-04-19 19:08:21 -0400532 cmds.append(['rsync', '-Rav'] + pkgs + [remote_location + '/'])
David Jamesc0f158a2011-02-22 16:07:29 -0800533 for cmd in cmds:
Peter Mayo193f68f2011-04-19 19:08:21 -0400534 if not _RetryRun(cmd, cwd=package_path):
535 raise UploadFailed('Could not run %r' % cmd)
David James8c846492011-01-25 17:07:29 -0800536
David James8fa34ea2011-04-15 13:00:20 -0700537 def _UploadBoardTarball(self, board_path, url_suffix):
538 """Upload a tarball of the board at the specified path to Google Storage.
539
540 Args:
541 board_path: The path to the board dir.
542 url_suffix: The remote subdirectory where we should upload the packages.
543 """
544 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
545 assert remote_location.startswith('gs://')
546 cwd, boardname = os.path.split(board_path.rstrip(os.path.sep))
547 tmpdir = tempfile.mkdtemp()
548 try:
549 tarfile = os.path.join(tmpdir, '%s.tbz2' % boardname)
550 cmd = ['sudo', 'tar', '-I', 'pbzip2', '-cf', tarfile]
551 excluded_paths = ('usr/lib/debug', 'usr/local/autotest', 'packages',
552 'tmp')
553 for path in excluded_paths:
554 cmd.append('--exclude=%s/%s/*' % (boardname, path))
555 cmd.append(boardname)
556 cros_build_lib.RunCommand(cmd, cwd=cwd)
557 remote_tarfile = '%s/%s.tbz2' % (remote_location.rstrip('/'), boardname)
558 if _GsUpload((tarfile, remote_tarfile, self._acl)):
559 sys.exit(1)
560 finally:
561 cros_build_lib.RunCommand(['sudo', 'rm', '-rf', tmpdir], cwd=cwd)
562
David Jamesc0f158a2011-02-22 16:07:29 -0800563 def _SyncHostPrebuilts(self, build_path, version, key, git_sync,
564 sync_binhost_conf):
565 """Synchronize host prebuilt files.
David James05bcb2b2011-02-09 09:25:47 -0800566
David Jamesc0f158a2011-02-22 16:07:29 -0800567 This function will sync both the standard host packages, plus the host
568 packages associated with all targets that have been "setup" with the
569 current host's chroot. For instance, if this host has been used to build
570 x86-generic, it will sync the host packages associated with
571 'i686-pc-linux-gnu'. If this host has also been used to build arm-generic,
572 it will also sync the host packages associated with
573 'armv7a-cros-linux-gnueabi'.
David James05bcb2b2011-02-09 09:25:47 -0800574
David Jamesc0f158a2011-02-22 16:07:29 -0800575 Args:
576 build_path: The path to the directory containing the chroot.
577 version: A unique string, intended to be included in the upload path,
578 which identifies the version number of the uploaded prebuilts.
579 key: The variable key to update in the git file.
580 git_sync: If set, update make.conf of target to reference the latest
581 prebuilt packages generated here.
582 sync_binhost_conf: If set, update binhost config file in
583 chromiumos-overlay for the host.
584 """
585 # Upload prebuilts.
586 package_path = os.path.join(build_path, _HOST_PACKAGES_PATH)
587 url_suffix = _REL_HOST_PATH % {'version': version, 'target': _HOST_TARGET}
David James8fa34ea2011-04-15 13:00:20 -0700588 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
589 self._UploadPrebuilt(package_path, packages_url_suffix)
David James05bcb2b2011-02-09 09:25:47 -0800590
David Jamesc0f158a2011-02-22 16:07:29 -0800591 # Record URL where prebuilts were uploaded.
592 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
David Jamesf0e6fd72011-04-15 15:58:07 -0700593 packages_url_suffix.rstrip('/'))
David Jamesc0f158a2011-02-22 16:07:29 -0800594 if git_sync:
595 git_file = os.path.join(build_path, _PREBUILT_MAKE_CONF[_HOST_TARGET])
596 RevGitFile(git_file, url_value, key=key)
597 if sync_binhost_conf:
598 binhost_conf = os.path.join(build_path, _BINHOST_CONF_DIR, 'host',
599 '%s-%s.conf' % (_HOST_TARGET, key))
600 UpdateBinhostConfFile(binhost_conf, key, url_value)
601
602 def _SyncBoardPrebuilts(self, board, build_path, version, key, git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700603 sync_binhost_conf, upload_board_tarball):
David Jamesc0f158a2011-02-22 16:07:29 -0800604 """Synchronize board prebuilt files.
605
606 Args:
607 board: The board to upload to Google Storage.
608 build_path: The path to the directory containing the chroot.
609 version: A unique string, intended to be included in the upload path,
610 which identifies the version number of the uploaded prebuilts.
611 key: The variable key to update in the git file.
612 git_sync: If set, update make.conf of target to reference the latest
613 prebuilt packages generated here.
614 sync_binhost_conf: If set, update binhost config file in
615 chromiumos-overlay for the current board.
David James8fa34ea2011-04-15 13:00:20 -0700616 upload_board_tarball: Include a tarball of the board in our upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800617 """
David Jamesc0f158a2011-02-22 16:07:29 -0800618 board_path = os.path.join(build_path, _BOARD_PATH % {'board': board})
619 package_path = os.path.join(board_path, 'packages')
620 url_suffix = _REL_BOARD_PATH % {'board': board, 'version': version}
David James8fa34ea2011-04-15 13:00:20 -0700621 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
622
623 # Upload board tarballs in the background.
624 if upload_board_tarball:
625 tar_process = multiprocessing.Process(target=self._UploadBoardTarball,
626 args=(board_path, url_suffix))
627 tar_process.start()
628
629 # Upload prebuilts.
630 self._UploadPrebuilt(package_path, packages_url_suffix)
631
632 # Make sure we finished uploading the board tarballs.
633 if upload_board_tarball:
634 tar_process.join()
635 assert tar_process.exitcode == 0
David Jamesc0f158a2011-02-22 16:07:29 -0800636
637 # Record URL where prebuilts were uploaded.
638 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
David Jamesf0e6fd72011-04-15 15:58:07 -0700639 packages_url_suffix.rstrip('/'))
David Jamesc0f158a2011-02-22 16:07:29 -0800640 if git_sync:
641 git_file = DeterminePrebuiltConfFile(build_path, board)
642 RevGitFile(git_file, url_value, key=key)
643 if sync_binhost_conf:
644 binhost_conf = os.path.join(build_path, _BINHOST_CONF_DIR, 'target',
645 '%s-%s.conf' % (board, key))
646 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800647
648
David James8c846492011-01-25 17:07:29 -0800649def usage(parser, msg):
650 """Display usage message and parser help then exit with 1."""
651 print >> sys.stderr, msg
652 parser.print_help()
653 sys.exit(1)
654
David Jamesc0f158a2011-02-22 16:07:29 -0800655def ParseOptions():
David James8c846492011-01-25 17:07:29 -0800656 parser = optparse.OptionParser()
657 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
658 default=_BINHOST_BASE_URL,
659 help='Base URL to use for binhost in make.conf updates')
660 parser.add_option('', '--previous-binhost-url', action='append',
661 default=[], dest='previous_binhost_url',
662 help='Previous binhost URL')
663 parser.add_option('-b', '--board', dest='board', default=None,
664 help='Board type that was built on this machine')
665 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800666 help='Path to the directory containing the chroot')
David James8c846492011-01-25 17:07:29 -0800667 parser.add_option('-s', '--sync-host', dest='sync_host',
668 default=False, action='store_true',
669 help='Sync host prebuilts')
670 parser.add_option('-g', '--git-sync', dest='git_sync',
671 default=False, action='store_true',
672 help='Enable git version sync (This commits to a repo)')
673 parser.add_option('-u', '--upload', dest='upload',
674 default=None,
675 help='Upload location')
676 parser.add_option('-V', '--prepend-version', dest='prepend_version',
677 default=None,
678 help='Add an identifier to the front of the version')
679 parser.add_option('-f', '--filters', dest='filters', action='store_true',
680 default=False,
681 help='Turn on filtering of private ebuild packages')
682 parser.add_option('-k', '--key', dest='key',
683 default='PORTAGE_BINHOST',
684 help='Key to update in make.conf / binhost.conf')
685 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
686 default=False, action='store_true',
687 help='Update binhost.conf')
David Jamesfd0b0852011-02-23 11:15:36 -0800688 parser.add_option('-P', '--private', dest='private', action='store_true',
689 default=False, help='Mark gs:// uploads as private.')
David James8fa34ea2011-04-15 13:00:20 -0700690 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
691 action='store_true', default=False,
692 help='Upload board tarball to Google Storage.')
David James8c846492011-01-25 17:07:29 -0800693
694 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800695 if not options.build_path:
696 usage(parser, 'Error: you need provide a chroot path')
David James8c846492011-01-25 17:07:29 -0800697 if not options.upload:
698 usage(parser, 'Error: you need to provide an upload location using -u')
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700699
David James8fa34ea2011-04-15 13:00:20 -0700700
701 if options.upload_board_tarball and not options.upload.startswith('gs://'):
702 usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
703 '--upload must be a gs:// URL.')
704
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700705 if options.private:
706 if options.sync_host:
707 usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
708 'together, we do not support private host prebuilts')
709
710 if not options.upload.startswith('gs://'):
711 usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
712 '--upload must be a gs:// URL.')
713
714 if options.binhost_base_url != _BINHOST_BASE_URL:
715 usage(parser, 'Error: when using --private the --binhost-base-url '
716 'is automatically derived.')
David Jamesc0f158a2011-02-22 16:07:29 -0800717 return options
718
719def main():
720 options = ParseOptions()
721
David James8c846492011-01-25 17:07:29 -0800722 if options.filters:
723 LoadPrivateFilters(options.build_path)
724
David Jamesfd0b0852011-02-23 11:15:36 -0800725
David James05bcb2b2011-02-09 09:25:47 -0800726 # Calculate a list of Packages index files to compare against. Whenever we
727 # upload a package, we check to make sure it's not already stored in one of
728 # the packages files we uploaded. This list of packages files might contain
729 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800730 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800731
David Jamesc0f158a2011-02-22 16:07:29 -0800732 version = GetVersion()
733 if options.prepend_version:
734 version = '%s-%s' % (options.prepend_version, version)
735
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700736 acl = 'public-read'
737 binhost_base_url = options.binhost_base_url
738
739 if options.private:
740 binhost_base_url = options.upload
741 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
742 options.board)
743 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
744
745 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David Jamesc0f158a2011-02-22 16:07:29 -0800746 pkg_indexes)
747
David James8c846492011-01-25 17:07:29 -0800748 if options.sync_host:
David Jamesc0f158a2011-02-22 16:07:29 -0800749 uploader._SyncHostPrebuilts(options.build_path, version, options.key,
750 options.git_sync, options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800751
752 if options.board:
David Jamesc0f158a2011-02-22 16:07:29 -0800753 uploader._SyncBoardPrebuilts(options.board, options.build_path, version,
754 options.key, options.git_sync,
David James8fa34ea2011-04-15 13:00:20 -0700755 options.sync_binhost_conf,
756 options.upload_board_tarball)
David James8c846492011-01-25 17:07:29 -0800757
758if __name__ == '__main__':
759 main()