blob: 438ea536ed92f4314836b1da7ed98a4218270478 [file] [log] [blame]
David James8c846492011-01-25 17:07:29 -08001#!/usr/bin/python
Chris Sosac13bba52011-05-24 15:14:09 -07002# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
David James8c846492011-01-25 17:07:29 -08003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Brian Harringaf019fb2012-05-10 15:06:13 -07006"""This script is used to upload host prebuilts as well as board BINHOSTS.
David James8c846492011-01-25 17:07:29 -08007
8If the URL starts with 'gs://', we upload using gsutil to Google Storage.
9Otherwise, rsync is used.
10
11After a build is successfully uploaded a file is updated with the proper
12BINHOST version as well as the target board. This file is defined in GIT_FILE
13
14
15To read more about prebuilts/binhost binary packages please refer to:
16http://sites/chromeos/for-team-members/engineering/releng/prebuilt-binaries-for-streamlining-the-build-process
17
18
19Example of uploading prebuilt amd64 host files to Google Storage:
20./prebuilt.py -p /b/cbuild/build -s -u gs://chromeos-prebuilt
21
22Example of uploading x86-dogfood binhosts to Google Storage:
23./prebuilt.py -b x86-dogfood -p /b/cbuild/build/ -u gs://chromeos-prebuilt -g
24
25Example of uploading prebuilt amd64 host files using rsync:
26./prebuilt.py -p /b/cbuild/build -s -u codf30.jail:/tmp
27"""
28
Chris Sosa1dc96132012-05-11 15:40:50 -070029import datetime
30import multiprocessing
31import optparse
32import os
33import sys
34import tempfile
35
36if __name__ == '__main__':
37 import constants
38 sys.path.insert(0, constants.SOURCE_ROOT)
39
40from chromite.lib import cros_build_lib
Brian Harringaf019fb2012-05-10 15:06:13 -070041from chromite.lib import osutils
Chris Sosa1dc96132012-05-11 15:40:50 -070042from chromite.lib.binpkg import (GrabLocalPackageIndex, GrabRemotePackageIndex)
43
David James8c846492011-01-25 17:07:29 -080044_RETRIES = 3
45_GSUTIL_BIN = '/b/build/third_party/gsutil/gsutil'
46_HOST_PACKAGES_PATH = 'chroot/var/lib/portage/pkgs'
David James05bcb2b2011-02-09 09:25:47 -080047_CATEGORIES_PATH = 'chroot/etc/portage/categories'
David James615e5b52011-06-03 11:10:15 -070048_PYM_PATH = 'chroot/usr/lib/portage/pym'
David James4058b0d2011-12-08 21:24:50 -080049_HOST_ARCH = 'amd64'
David James8c846492011-01-25 17:07:29 -080050_BOARD_PATH = 'chroot/build/%(board)s'
David James4058b0d2011-12-08 21:24:50 -080051_REL_BOARD_PATH = 'board/%(target)s/%(version)s'
52_REL_HOST_PATH = 'host/%(host_arch)s/%(target)s/%(version)s'
David James8c846492011-01-25 17:07:29 -080053# Private overlays to look at for builds to filter
54# relative to build path
55_PRIVATE_OVERLAY_DIR = 'src/private-overlays'
Scott Zawalskiab1bed32011-03-16 15:24:24 -070056_GOOGLESTORAGE_ACL_FILE = 'googlestorage_acl.xml'
David Jamesce619292011-11-08 11:42:36 -080057_BINHOST_BASE_URL = 'https://commondatastorage.googleapis.com/chromeos-prebuilt'
David James8c846492011-01-25 17:07:29 -080058_PREBUILT_BASE_DIR = 'src/third_party/chromiumos-overlay/chromeos/config/'
59# Created in the event of new host targets becoming available
60_PREBUILT_MAKE_CONF = {'amd64': os.path.join(_PREBUILT_BASE_DIR,
61 'make.conf.amd64-host')}
62_BINHOST_CONF_DIR = 'src/third_party/chromiumos-overlay/chromeos/binhost'
63
64
David James8c846492011-01-25 17:07:29 -080065class UploadFailed(Exception):
66 """Raised when one of the files uploaded failed."""
67 pass
68
69class UnknownBoardFormat(Exception):
70 """Raised when a function finds an unknown board format."""
71 pass
72
David James8c846492011-01-25 17:07:29 -080073
David James4058b0d2011-12-08 21:24:50 -080074class BuildTarget(object):
75 """A board/variant/profile tuple."""
76
77 def __init__(self, board_variant, profile=None):
78 self.board_variant = board_variant
79 self.board, _, self.variant = board_variant.partition('_')
80 self.profile = profile
81
82 def __str__(self):
83 if self.profile:
84 return '%s_%s' % (self.board_variant, self.profile)
85 else:
86 return self.board_variant
87
88 def __eq__(self, other):
89 return str(other) == str(self)
90
91 def __hash__(self):
92 return hash(str(self))
93
94
David James8c846492011-01-25 17:07:29 -080095def UpdateLocalFile(filename, value, key='PORTAGE_BINHOST'):
96 """Update the key in file with the value passed.
97 File format:
98 key="value"
99 Note quotes are added automatically
100
101 Args:
102 filename: Name of file to modify.
103 value: Value to write with the key.
104 key: The variable key to update. (Default: PORTAGE_BINHOST)
105 """
106 if os.path.exists(filename):
107 file_fh = open(filename)
108 else:
109 file_fh = open(filename, 'w+')
110 file_lines = []
111 found = False
112 keyval_str = '%(key)s=%(value)s'
113 for line in file_fh:
114 # Strip newlines from end of line. We already add newlines below.
115 line = line.rstrip("\n")
116
117 if len(line.split('=')) != 2:
118 # Skip any line that doesn't fit key=val.
119 file_lines.append(line)
120 continue
121
122 file_var, file_val = line.split('=')
123 if file_var == key:
124 found = True
David James20b2b6f2011-11-18 15:11:58 -0800125 print 'Updating %s=%s to %s="%s"' % (file_var, file_val, key, value)
David James8c846492011-01-25 17:07:29 -0800126 value = '"%s"' % value
127 file_lines.append(keyval_str % {'key': key, 'value': value})
128 else:
129 file_lines.append(keyval_str % {'key': file_var, 'value': file_val})
130
131 if not found:
Brian Harring2a014302012-05-12 00:53:33 -0700132 value = '"%s"' % value
David James8c846492011-01-25 17:07:29 -0800133 file_lines.append(keyval_str % {'key': key, 'value': value})
134
135 file_fh.close()
136 # write out new file
Brian Harringaf019fb2012-05-10 15:06:13 -0700137 osutils.WriteFile(filename, '\n'.join(file_lines) + '\n')
David James8c846492011-01-25 17:07:29 -0800138
139
David James27fa7d12011-06-29 17:24:14 -0700140def RevGitFile(filename, value, retries=5, key='PORTAGE_BINHOST', dryrun=False):
David James8c846492011-01-25 17:07:29 -0800141 """Update and push the git file.
142
143 Args:
144 filename: file to modify that is in a git repo already
145 value: string representing the version of the prebuilt that has been
146 uploaded.
147 retries: The number of times to retry before giving up, default: 5
148 key: The variable key to update in the git file.
149 (Default: PORTAGE_BINHOST)
150 """
151 prebuilt_branch = 'prebuilt_branch'
David James1b6e67a2011-05-19 21:32:38 -0700152 cwd = os.path.abspath(os.path.dirname(filename))
Brian Harring609dc4e2012-05-07 02:17:44 -0700153 commit = cros_build_lib.RunGitCommand(
154 cwd, ['rev-parse', 'HEAD']).output.rstrip()
David James8c846492011-01-25 17:07:29 -0800155 description = 'Update %s="%s" in %s' % (key, value, filename)
156 print description
David James66009462012-03-25 10:08:38 -0700157
David James8c846492011-01-25 17:07:29 -0800158 try:
David James66009462012-03-25 10:08:38 -0700159 cros_build_lib.CreatePushBranch(prebuilt_branch, cwd)
David James8c846492011-01-25 17:07:29 -0800160 UpdateLocalFile(filename, value, key)
Brian Harring609dc4e2012-05-07 02:17:44 -0700161 cros_build_lib.RunGitCommand(cwd, ['add', filename])
162 cros_build_lib.RunGitCommand(cwd, ['commit', '-m', description])
163 cros_build_lib.GitPushWithRetry(prebuilt_branch, cwd, dryrun=dryrun,
David James66009462012-03-25 10:08:38 -0700164 retries=retries)
David James8c846492011-01-25 17:07:29 -0800165 finally:
David James1b6e67a2011-05-19 21:32:38 -0700166 cros_build_lib.RunCommand(['git', 'checkout', commit], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800167
168
169def GetVersion():
170 """Get the version to put in LATEST and update the git version with."""
171 return datetime.datetime.now().strftime('%d.%m.%y.%H%M%S')
172
173
David James8c846492011-01-25 17:07:29 -0800174def _GsUpload(args):
175 """Upload to GS bucket.
176
177 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800178 args: a tuple of three arguments that contains local_file, remote_file, and
179 the acl used for uploading the file.
David James8c846492011-01-25 17:07:29 -0800180
181 Returns:
182 Return the arg tuple of two if the upload failed
183 """
David Jamesfd0b0852011-02-23 11:15:36 -0800184 (local_file, remote_file, acl) = args
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700185 CANNED_ACLS = ['public-read', 'private', 'bucket-owner-read',
186 'authenticated-read', 'bucket-owner-full-control',
187 'public-read-write']
188 acl_cmd = None
189 if acl in CANNED_ACLS:
Peter Mayo193f68f2011-04-19 19:08:21 -0400190 cmd = [_GSUTIL_BIN, 'cp', '-a', acl, local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700191 else:
192 # For private uploads we assume that the overlay board is set up properly
193 # and a googlestore_acl.xml is present, if not this script errors
Peter Mayo193f68f2011-04-19 19:08:21 -0400194 cmd = [_GSUTIL_BIN, 'cp', '-a', 'private', local_file, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700195 if not os.path.exists(acl):
196 print >> sys.stderr, ('You are specifying either a file that does not '
197 'exist or an unknown canned acl: %s. Aborting '
198 'upload') % acl
199 # emulate the failing of an upload since we are not uploading the file
200 return (local_file, remote_file)
David James8c846492011-01-25 17:07:29 -0800201
Peter Mayo193f68f2011-04-19 19:08:21 -0400202 acl_cmd = [_GSUTIL_BIN, 'setacl', acl, remote_file]
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700203
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700204 if not cros_build_lib.RunCommandWithRetries(
205 _RETRIES, cmd, print_cmd=False, error_code_ok=True).returncode == 0:
David James8c846492011-01-25 17:07:29 -0800206 return (local_file, remote_file)
207
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700208 if acl_cmd:
209 # Apply the passed in ACL xml file to the uploaded object.
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700210 cros_build_lib.RunCommandWithRetries(_RETRIES, acl_cmd, print_cmd=False,
211 error_code_ok=True)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700212
213
David Jamesfd0b0852011-02-23 11:15:36 -0800214def RemoteUpload(acl, files, pool=10):
David James8c846492011-01-25 17:07:29 -0800215 """Upload to google storage.
216
217 Create a pool of process and call _GsUpload with the proper arguments.
218
219 Args:
David Jamesfd0b0852011-02-23 11:15:36 -0800220 acl: The canned acl used for uploading. acl can be one of: "public-read",
221 "public-read-write", "authenticated-read", "bucket-owner-read",
222 "bucket-owner-full-control", or "private".
David James8c846492011-01-25 17:07:29 -0800223 files: dictionary with keys to local files and values to remote path.
224 pool: integer of maximum proesses to have at the same time.
225
226 Returns:
227 Return a set of tuple arguments of the failed uploads
228 """
229 # TODO(scottz) port this to use _RunManyParallel when it is available in
230 # cros_build_lib
231 pool = multiprocessing.Pool(processes=pool)
232 workers = []
233 for local_file, remote_path in files.iteritems():
David Jamesfd0b0852011-02-23 11:15:36 -0800234 workers.append((local_file, remote_path, acl))
David James8c846492011-01-25 17:07:29 -0800235
236 result = pool.map_async(_GsUpload, workers, chunksize=1)
237 while True:
238 try:
Chris Sosa471532a2011-02-01 15:10:06 -0800239 return set(result.get(60 * 60))
David James8c846492011-01-25 17:07:29 -0800240 except multiprocessing.TimeoutError:
241 pass
242
243
244def GenerateUploadDict(base_local_path, base_remote_path, pkgs):
245 """Build a dictionary of local remote file key pairs to upload.
246
247 Args:
248 base_local_path: The base path to the files on the local hard drive.
249 remote_path: The base path to the remote paths.
250 pkgs: The packages to upload.
251
252 Returns:
253 Returns a dictionary of local_path/remote_path pairs
254 """
255 upload_files = {}
256 for pkg in pkgs:
257 suffix = pkg['CPV'] + '.tbz2'
258 local_path = os.path.join(base_local_path, suffix)
259 assert os.path.exists(local_path)
260 remote_path = '%s/%s' % (base_remote_path.rstrip('/'), suffix)
261 upload_files[local_path] = remote_path
262
263 return upload_files
264
265def GetBoardPathFromCrosOverlayList(build_path, target):
266 """Use the cros_overlay_list to determine the path to the board overlay
267 Args:
268 build_path: The path to the root of the build directory
269 target: The target that we are looking for, could consist of board and
270 board_variant, we handle that properly
271 Returns:
272 The last line from cros_overlay_list as a string
273 """
Chris Sosa471532a2011-02-01 15:10:06 -0800274 script_dir = os.path.join(build_path, 'src/platform/dev/host')
David James4058b0d2011-12-08 21:24:50 -0800275 cmd = ['./cros_overlay_list', '--board', target.board]
276 if target.variant:
277 cmd += ['--variant', target.variant]
David James8c846492011-01-25 17:07:29 -0800278
279 cmd_output = cros_build_lib.RunCommand(cmd, redirect_stdout=True,
280 cwd=script_dir)
281 # We only care about the last entry
282 return cmd_output.output.splitlines().pop()
283
284
285def DeterminePrebuiltConfFile(build_path, target):
286 """Determine the prebuilt.conf file that needs to be updated for prebuilts.
287
288 Args:
289 build_path: The path to the root of the build directory
290 target: String representation of the board. This includes host and board
291 targets
292
293 Returns
294 A string path to a prebuilt.conf file to be updated.
295 """
David James4058b0d2011-12-08 21:24:50 -0800296 if _HOST_ARCH == target:
David James8c846492011-01-25 17:07:29 -0800297 # We are host.
298 # Without more examples of hosts this is a kludge for now.
299 # TODO(Scottz): as new host targets come online expand this to
300 # work more like boards.
Chris Sosa471532a2011-02-01 15:10:06 -0800301 make_path = _PREBUILT_MAKE_CONF[target]
David James8c846492011-01-25 17:07:29 -0800302 else:
303 # We are a board
304 board = GetBoardPathFromCrosOverlayList(build_path, target)
305 make_path = os.path.join(board, 'prebuilt.conf')
306
307 return make_path
308
309
310def UpdateBinhostConfFile(path, key, value):
311 """Update binhost config file file with key=value.
312
313 Args:
314 path: Filename to update.
315 key: Key to update.
316 value: New value for key.
317 """
318 cwd = os.path.dirname(os.path.abspath(path))
319 filename = os.path.basename(path)
Brian Harringaf019fb2012-05-10 15:06:13 -0700320 osutils.SafeMakedirs(cwd)
Brian Harring22edb442012-05-11 23:55:18 -0700321 osutils.WriteFile(path, '', mode='a')
David James8c846492011-01-25 17:07:29 -0800322 UpdateLocalFile(path, value, key)
Chris Sosac13bba52011-05-24 15:14:09 -0700323 cros_build_lib.RunCommand(['git', 'add', filename], cwd=cwd)
David James20b2b6f2011-11-18 15:11:58 -0800324 description = 'Update %s="%s" in %s' % (key, value, filename)
Peter Mayo193f68f2011-04-19 19:08:21 -0400325 cros_build_lib.RunCommand(['git', 'commit', '-m', description], cwd=cwd)
David James8c846492011-01-25 17:07:29 -0800326
327
David Jamesce093af2011-02-23 15:21:58 -0800328def _GrabAllRemotePackageIndexes(binhost_urls):
David James05bcb2b2011-02-09 09:25:47 -0800329 """Grab all of the packages files associated with a list of binhost_urls.
330
David James05bcb2b2011-02-09 09:25:47 -0800331 Args:
332 binhost_urls: The URLs for the directories containing the Packages files we
333 want to grab.
David James05bcb2b2011-02-09 09:25:47 -0800334
335 Returns:
336 A list of PackageIndex objects.
337 """
338 pkg_indexes = []
339 for url in binhost_urls:
340 pkg_index = GrabRemotePackageIndex(url)
341 if pkg_index:
342 pkg_indexes.append(pkg_index)
David James05bcb2b2011-02-09 09:25:47 -0800343 return pkg_indexes
344
345
David James05bcb2b2011-02-09 09:25:47 -0800346
David Jamesc0f158a2011-02-22 16:07:29 -0800347class PrebuiltUploader(object):
348 """Synchronize host and board prebuilts."""
David James8c846492011-01-25 17:07:29 -0800349
David James615e5b52011-06-03 11:10:15 -0700350 def __init__(self, upload_location, acl, binhost_base_url,
David James32b0b2f2011-07-13 20:56:50 -0700351 pkg_indexes, build_path, packages, skip_upload,
David James4058b0d2011-12-08 21:24:50 -0800352 binhost_conf_dir, debug, target, slave_targets):
David Jamesc0f158a2011-02-22 16:07:29 -0800353 """Constructor for prebuilt uploader object.
David James8c846492011-01-25 17:07:29 -0800354
David Jamesc0f158a2011-02-22 16:07:29 -0800355 This object can upload host or prebuilt files to Google Storage.
David James8c846492011-01-25 17:07:29 -0800356
David Jamesc0f158a2011-02-22 16:07:29 -0800357 Args:
358 upload_location: The upload location.
David Jamesfd0b0852011-02-23 11:15:36 -0800359 acl: The canned acl used for uploading to Google Storage. acl can be one
360 of: "public-read", "public-read-write", "authenticated-read",
361 "bucket-owner-read", "bucket-owner-full-control", or "private". If
362 we are not uploading to Google Storage, this parameter is unused.
363 binhost_base_url: The URL used for downloading the prebuilts.
David Jamesc0f158a2011-02-22 16:07:29 -0800364 pkg_indexes: Old uploaded prebuilts to compare against. Instead of
365 uploading duplicate files, we just link to the old files.
David James615e5b52011-06-03 11:10:15 -0700366 build_path: The path to the directory containing the chroot.
367 packages: Packages to upload.
David James32b0b2f2011-07-13 20:56:50 -0700368 skip_upload: Don't actually upload the tarballs.
369 binhost_conf_dir: Directory where to store binhost.conf files.
370 debug: Don't push or upload prebuilts.
David James4058b0d2011-12-08 21:24:50 -0800371 target: BuildTarget managed by this builder.
372 slave_targets: List of BuildTargets managed by slave builders.
David Jamesc0f158a2011-02-22 16:07:29 -0800373 """
374 self._upload_location = upload_location
David Jamesfd0b0852011-02-23 11:15:36 -0800375 self._acl = acl
David Jamesc0f158a2011-02-22 16:07:29 -0800376 self._binhost_base_url = binhost_base_url
377 self._pkg_indexes = pkg_indexes
David James615e5b52011-06-03 11:10:15 -0700378 self._build_path = build_path
379 self._packages = set(packages)
David James8ece7ee2011-06-29 16:02:30 -0700380 self._skip_upload = skip_upload
David James32b0b2f2011-07-13 20:56:50 -0700381 self._binhost_conf_dir = binhost_conf_dir
David James27fa7d12011-06-29 17:24:14 -0700382 self._debug = debug
David James4058b0d2011-12-08 21:24:50 -0800383 self._target = target
384 self._slave_targets = slave_targets
David James615e5b52011-06-03 11:10:15 -0700385
386 def _ShouldFilterPackage(self, pkg):
387 if not self._packages:
388 return False
389 pym_path = os.path.abspath(os.path.join(self._build_path, _PYM_PATH))
David James710b7dc2012-02-07 16:49:59 -0800390 sys.path.insert(0, pym_path)
David James615e5b52011-06-03 11:10:15 -0700391 import portage.versions
392 cat, pkgname = portage.versions.catpkgsplit(pkg['CPV'])[0:2]
393 cp = '%s/%s' % (cat, pkgname)
394 return pkgname not in self._packages and cp not in self._packages
David James8c846492011-01-25 17:07:29 -0800395
David Jamesc0f158a2011-02-22 16:07:29 -0800396 def _UploadPrebuilt(self, package_path, url_suffix):
397 """Upload host or board prebuilt files to Google Storage space.
David James8c846492011-01-25 17:07:29 -0800398
David Jamesc0f158a2011-02-22 16:07:29 -0800399 Args:
400 package_path: The path to the packages dir.
David Jamesce093af2011-02-23 15:21:58 -0800401 url_suffix: The remote subdirectory where we should upload the packages.
David Jamesa3bba142011-05-26 21:24:20 -0700402
David Jamesc0f158a2011-02-22 16:07:29 -0800403 """
David James8c846492011-01-25 17:07:29 -0800404
David Jamesc0f158a2011-02-22 16:07:29 -0800405 # Process Packages file, removing duplicates and filtered packages.
406 pkg_index = GrabLocalPackageIndex(package_path)
407 pkg_index.SetUploadLocation(self._binhost_base_url, url_suffix)
David James615e5b52011-06-03 11:10:15 -0700408 pkg_index.RemoveFilteredPackages(self._ShouldFilterPackage)
David Jamesc0f158a2011-02-22 16:07:29 -0800409 uploads = pkg_index.ResolveDuplicateUploads(self._pkg_indexes)
David James05bcb2b2011-02-09 09:25:47 -0800410
David Jamesc0f158a2011-02-22 16:07:29 -0800411 # Write Packages file.
412 tmp_packages_file = pkg_index.WriteToNamedTemporaryFile()
David James05bcb2b2011-02-09 09:25:47 -0800413
David Jamesc0f158a2011-02-22 16:07:29 -0800414 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
415 if remote_location.startswith('gs://'):
416 # Build list of files to upload.
417 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
418 remote_file = '%s/Packages' % remote_location.rstrip('/')
419 upload_files[tmp_packages_file.name] = remote_file
David James05bcb2b2011-02-09 09:25:47 -0800420
David Jamesfd0b0852011-02-23 11:15:36 -0800421 failed_uploads = RemoteUpload(self._acl, upload_files)
David Jamesc0f158a2011-02-22 16:07:29 -0800422 if len(failed_uploads) > 1 or (None not in failed_uploads):
David Jamesf4db1122011-03-17 16:18:05 -0700423 error_msg = ['%s -> %s\n' % args for args in failed_uploads if args]
David Jamesc0f158a2011-02-22 16:07:29 -0800424 raise UploadFailed('Error uploading:\n%s' % error_msg)
425 else:
Peter Mayo193f68f2011-04-19 19:08:21 -0400426 pkgs = [p['CPV'] + '.tbz2' for p in uploads]
David Jamesc0f158a2011-02-22 16:07:29 -0800427 ssh_server, remote_path = remote_location.split(':', 1)
Peter Mayo193f68f2011-04-19 19:08:21 -0400428 remote_path = remote_path.rstrip('/')
429 pkg_index = tmp_packages_file.name
430 remote_location = remote_location.rstrip('/')
431 remote_packages = '%s/Packages' % remote_location
432 cmds = [['ssh', ssh_server, 'mkdir', '-p', remote_path],
433 ['rsync', '-av', '--chmod=a+r', pkg_index, remote_packages]]
David Jamesc0f158a2011-02-22 16:07:29 -0800434 if pkgs:
Peter Mayo193f68f2011-04-19 19:08:21 -0400435 cmds.append(['rsync', '-Rav'] + pkgs + [remote_location + '/'])
David Jamesc0f158a2011-02-22 16:07:29 -0800436 for cmd in cmds:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700437 try:
438 cros_build_lib.RunCommandWithRetries(_RETRIES, cmd, cwd=package_path)
439 except cros_build_lib.RunCommandError:
440 raise UploadFailed('Could not run %s' % cmd)
David James8c846492011-01-25 17:07:29 -0800441
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200442 def _UploadBoardTarball(self, board_path, url_suffix, version):
David James8fa34ea2011-04-15 13:00:20 -0700443 """Upload a tarball of the board at the specified path to Google Storage.
444
445 Args:
446 board_path: The path to the board dir.
447 url_suffix: The remote subdirectory where we should upload the packages.
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200448 version: The version of the board.
David James8fa34ea2011-04-15 13:00:20 -0700449 """
450 remote_location = '%s/%s' % (self._upload_location.rstrip('/'), url_suffix)
451 assert remote_location.startswith('gs://')
452 cwd, boardname = os.path.split(board_path.rstrip(os.path.sep))
453 tmpdir = tempfile.mkdtemp()
454 try:
455 tarfile = os.path.join(tmpdir, '%s.tbz2' % boardname)
Brian Harringd223a242012-02-03 20:12:10 -0800456 cmd = ['tar', '-I', 'pbzip2', '-cf', tarfile]
David James8fa34ea2011-04-15 13:00:20 -0700457 excluded_paths = ('usr/lib/debug', 'usr/local/autotest', 'packages',
458 'tmp')
459 for path in excluded_paths:
Zdenek Behane3ed3462011-06-16 00:36:08 +0200460 cmd.append('--exclude=%s/*' % path)
461 cmd.append('.')
Brian Harringd223a242012-02-03 20:12:10 -0800462 cros_build_lib.SudoRunCommand(cmd, cwd=os.path.join(cwd, boardname))
David James8fa34ea2011-04-15 13:00:20 -0700463 remote_tarfile = '%s/%s.tbz2' % (remote_location.rstrip('/'), boardname)
Zdenek Behan5ad96c02011-06-23 01:04:06 +0200464 # FIXME(zbehan): Temporary hack to upload amd64-host chroots to a
465 # different gs bucket. The right way is to do the upload in a separate
466 # pass of this script.
467 if boardname == 'amd64-host':
Zdenek Behanaf3c9002011-06-24 10:07:40 +0200468 # FIXME(zbehan): Why does version contain the prefix "chroot-"?
469 remote_tarfile = \
470 'gs://chromiumos-sdk/cros-sdk-%s.tbz2' % version.strip('chroot-')
David James8fa34ea2011-04-15 13:00:20 -0700471 if _GsUpload((tarfile, remote_tarfile, self._acl)):
472 sys.exit(1)
473 finally:
Brian Harringd223a242012-02-03 20:12:10 -0800474 cros_build_lib.SudoRunCommand(['rm', '-rf', tmpdir], cwd=cwd)
David James8fa34ea2011-04-15 13:00:20 -0700475
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700476 def _GetTargets(self):
477 """Retuns the list of targets to use."""
478 targets = self._slave_targets[:]
479 if self._target:
480 targets.append(self._target)
481
482 return targets
483
484 def SyncHostPrebuilts(self, version, key, git_sync, sync_binhost_conf):
David Jamesc0f158a2011-02-22 16:07:29 -0800485 """Synchronize host prebuilt files.
David James05bcb2b2011-02-09 09:25:47 -0800486
David Jamesc0f158a2011-02-22 16:07:29 -0800487 This function will sync both the standard host packages, plus the host
488 packages associated with all targets that have been "setup" with the
489 current host's chroot. For instance, if this host has been used to build
490 x86-generic, it will sync the host packages associated with
491 'i686-pc-linux-gnu'. If this host has also been used to build arm-generic,
492 it will also sync the host packages associated with
493 'armv7a-cros-linux-gnueabi'.
David James05bcb2b2011-02-09 09:25:47 -0800494
David Jamesc0f158a2011-02-22 16:07:29 -0800495 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800496 version: A unique string, intended to be included in the upload path,
497 which identifies the version number of the uploaded prebuilts.
498 key: The variable key to update in the git file.
499 git_sync: If set, update make.conf of target to reference the latest
500 prebuilt packages generated here.
501 sync_binhost_conf: If set, update binhost config file in
502 chromiumos-overlay for the host.
503 """
David Jamese2488642011-11-14 16:15:20 -0800504 # Slave boards are listed before the master board so that the master board
505 # takes priority (i.e. x86-generic preflight host prebuilts takes priority
506 # over preflight host prebuilts from other builders.)
507 binhost_urls = []
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700508 for target in self._GetTargets():
David James4058b0d2011-12-08 21:24:50 -0800509 url_suffix = _REL_HOST_PATH % {'version': version,
510 'host_arch': _HOST_ARCH,
511 'target': target}
David Jamese2488642011-11-14 16:15:20 -0800512 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James05bcb2b2011-02-09 09:25:47 -0800513
David James4058b0d2011-12-08 21:24:50 -0800514 if self._target == target and not self._skip_upload and not self._debug:
David Jamese2488642011-11-14 16:15:20 -0800515 # Upload prebuilts.
516 package_path = os.path.join(self._build_path, _HOST_PACKAGES_PATH)
517 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8ece7ee2011-06-29 16:02:30 -0700518
David Jamese2488642011-11-14 16:15:20 -0800519 # Record URL where prebuilts were uploaded.
520 binhost_urls.append('%s/%s/' % (self._binhost_base_url.rstrip('/'),
521 packages_url_suffix.rstrip('/')))
522
David James20b2b6f2011-11-18 15:11:58 -0800523 binhost = ' '.join(binhost_urls)
David James8ece7ee2011-06-29 16:02:30 -0700524 if git_sync:
525 git_file = os.path.join(self._build_path,
David James4058b0d2011-12-08 21:24:50 -0800526 _PREBUILT_MAKE_CONF[_HOST_ARCH])
David Jamese2488642011-11-14 16:15:20 -0800527 RevGitFile(git_file, binhost, key=key, dryrun=self._debug)
David James8ece7ee2011-06-29 16:02:30 -0700528 if sync_binhost_conf:
David James32b0b2f2011-07-13 20:56:50 -0700529 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James4058b0d2011-12-08 21:24:50 -0800530 'host', '%s-%s.conf' % (_HOST_ARCH, key))
David Jamese2488642011-11-14 16:15:20 -0800531 UpdateBinhostConfFile(binhost_conf, key, binhost)
David Jamesc0f158a2011-02-22 16:07:29 -0800532
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700533 def SyncBoardPrebuilts(self, version, key, git_sync, sync_binhost_conf,
534 upload_board_tarball):
David Jamesc0f158a2011-02-22 16:07:29 -0800535 """Synchronize board prebuilt files.
536
537 Args:
David Jamesc0f158a2011-02-22 16:07:29 -0800538 version: A unique string, intended to be included in the upload path,
539 which identifies the version number of the uploaded prebuilts.
540 key: The variable key to update in the git file.
541 git_sync: If set, update make.conf of target to reference the latest
542 prebuilt packages generated here.
543 sync_binhost_conf: If set, update binhost config file in
544 chromiumos-overlay for the current board.
David James8fa34ea2011-04-15 13:00:20 -0700545 upload_board_tarball: Include a tarball of the board in our upload.
David Jamesc0f158a2011-02-22 16:07:29 -0800546 """
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700547 for target in self._GetTargets():
David Jamese2488642011-11-14 16:15:20 -0800548 board_path = os.path.join(self._build_path,
David James4058b0d2011-12-08 21:24:50 -0800549 _BOARD_PATH % {'board': target.board_variant})
David Jamese2488642011-11-14 16:15:20 -0800550 package_path = os.path.join(board_path, 'packages')
David James4058b0d2011-12-08 21:24:50 -0800551 url_suffix = _REL_BOARD_PATH % {'target': target, 'version': version}
David Jamese2488642011-11-14 16:15:20 -0800552 packages_url_suffix = '%s/packages' % url_suffix.rstrip('/')
David James8fa34ea2011-04-15 13:00:20 -0700553
David James4058b0d2011-12-08 21:24:50 -0800554 if self._target == target and not self._skip_upload and not self._debug:
David Jamese2488642011-11-14 16:15:20 -0800555 # Upload board tarballs in the background.
556 if upload_board_tarball:
557 tar_process = multiprocessing.Process(target=self._UploadBoardTarball,
558 args=(board_path, url_suffix,
559 version))
560 tar_process.start()
David James8fa34ea2011-04-15 13:00:20 -0700561
David Jamese2488642011-11-14 16:15:20 -0800562 # Upload prebuilts.
563 self._UploadPrebuilt(package_path, packages_url_suffix)
David James8fa34ea2011-04-15 13:00:20 -0700564
David Jamese2488642011-11-14 16:15:20 -0800565 # Make sure we finished uploading the board tarballs.
566 if upload_board_tarball:
567 tar_process.join()
568 assert tar_process.exitcode == 0
569 # TODO(zbehan): This should be done cleaner.
David James4058b0d2011-12-08 21:24:50 -0800570 if target.board == 'amd64-host':
David Jamese2488642011-11-14 16:15:20 -0800571 sdk_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James8ece7ee2011-06-29 16:02:30 -0700572 'host/sdk_version.conf')
David Jamese2488642011-11-14 16:15:20 -0800573 RevGitFile(sdk_conf, version.strip('chroot-'),
574 key='SDK_LATEST_VERSION', dryrun=self._debug)
David Jamesc0f158a2011-02-22 16:07:29 -0800575
David Jamese2488642011-11-14 16:15:20 -0800576 # Record URL where prebuilts were uploaded.
577 url_value = '%s/%s/' % (self._binhost_base_url.rstrip('/'),
578 packages_url_suffix.rstrip('/'))
579
580 if git_sync:
David James4058b0d2011-12-08 21:24:50 -0800581 git_file = DeterminePrebuiltConfFile(self._build_path, target)
David Jamese2488642011-11-14 16:15:20 -0800582 RevGitFile(git_file, url_value, key=key, dryrun=self._debug)
583 if sync_binhost_conf:
584 binhost_conf = os.path.join(self._build_path, self._binhost_conf_dir,
David James4058b0d2011-12-08 21:24:50 -0800585 'target', '%s-%s.conf' % (target, key))
David Jamese2488642011-11-14 16:15:20 -0800586 UpdateBinhostConfFile(binhost_conf, key, url_value)
David James05bcb2b2011-02-09 09:25:47 -0800587
588
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700589def Usage(parser, msg):
David James8c846492011-01-25 17:07:29 -0800590 """Display usage message and parser help then exit with 1."""
591 print >> sys.stderr, msg
592 parser.print_help()
593 sys.exit(1)
594
David James4058b0d2011-12-08 21:24:50 -0800595
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700596def _AddSlaveBoard(_option, _opt_str, value, parser):
597 """Callback that adds a slave board to the list of slave targets."""
David James4058b0d2011-12-08 21:24:50 -0800598 parser.values.slave_targets.append(BuildTarget(value))
599
600
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700601def _AddSlaveProfile(_option, _opt_str, value, parser):
602 """Callback that adds a slave profile to the list of slave targets."""
David James4058b0d2011-12-08 21:24:50 -0800603 if not parser.values.slave_targets:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700604 Usage(parser, 'Must specify --slave-board before --slave-profile')
David James4058b0d2011-12-08 21:24:50 -0800605 if parser.values.slave_targets[-1].profile is not None:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700606 Usage(parser, 'Cannot specify --slave-profile twice for same board')
David James4058b0d2011-12-08 21:24:50 -0800607 parser.values.slave_targets[-1].profile = value
608
609
David Jamesc0f158a2011-02-22 16:07:29 -0800610def ParseOptions():
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700611 """Returns options given by the user and the target specified.
612
613 Returns a tuple containing a parsed options object and BuildTarget.
614 target instance is None if no board is specified.
615 """
David James8c846492011-01-25 17:07:29 -0800616 parser = optparse.OptionParser()
617 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
618 default=_BINHOST_BASE_URL,
619 help='Base URL to use for binhost in make.conf updates')
620 parser.add_option('', '--previous-binhost-url', action='append',
621 default=[], dest='previous_binhost_url',
622 help='Previous binhost URL')
623 parser.add_option('-b', '--board', dest='board', default=None,
624 help='Board type that was built on this machine')
David James4058b0d2011-12-08 21:24:50 -0800625 parser.add_option('', '--profile', dest='profile', default=None,
626 help='Profile that was built on this machine')
627 parser.add_option('', '--slave-board', default=[], action='callback',
628 dest='slave_targets', type='string',
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700629 callback=_AddSlaveBoard,
David James4058b0d2011-12-08 21:24:50 -0800630 help='Board type that was built on a slave machine. To '
631 'add a profile to this board, use --slave-profile.')
632 parser.add_option('', '--slave-profile', action='callback', type='string',
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700633 callback=_AddSlaveProfile,
David James4058b0d2011-12-08 21:24:50 -0800634 help='Board profile that was built on a slave machine. '
635 'Applies to previous slave board.')
David James8c846492011-01-25 17:07:29 -0800636 parser.add_option('-p', '--build-path', dest='build_path',
David James05bcb2b2011-02-09 09:25:47 -0800637 help='Path to the directory containing the chroot')
David James615e5b52011-06-03 11:10:15 -0700638 parser.add_option('', '--packages', action='append',
639 default=[], dest='packages',
640 help='Only include the specified packages. '
641 '(Default is to include all packages.)')
David James8c846492011-01-25 17:07:29 -0800642 parser.add_option('-s', '--sync-host', dest='sync_host',
643 default=False, action='store_true',
644 help='Sync host prebuilts')
645 parser.add_option('-g', '--git-sync', dest='git_sync',
646 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800647 help='Enable git version sync (This commits to a repo.) '
648 'This is used by full builders to commit directly '
649 'to board overlays.')
David James8c846492011-01-25 17:07:29 -0800650 parser.add_option('-u', '--upload', dest='upload',
651 default=None,
652 help='Upload location')
653 parser.add_option('-V', '--prepend-version', dest='prepend_version',
654 default=None,
655 help='Add an identifier to the front of the version')
656 parser.add_option('-f', '--filters', dest='filters', action='store_true',
657 default=False,
658 help='Turn on filtering of private ebuild packages')
659 parser.add_option('-k', '--key', dest='key',
660 default='PORTAGE_BINHOST',
661 help='Key to update in make.conf / binhost.conf')
David James8ece7ee2011-06-29 16:02:30 -0700662 parser.add_option('', '--set-version', dest='set_version',
663 default=None,
664 help='Specify the version string')
David James8c846492011-01-25 17:07:29 -0800665 parser.add_option('', '--sync-binhost-conf', dest='sync_binhost_conf',
666 default=False, action='store_true',
David Jamese2488642011-11-14 16:15:20 -0800667 help='Update binhost.conf in chromiumos-overlay or '
668 'chromeos-overlay. Commit the changes, but don\'t '
669 'push them. This is used for preflight binhosts.')
David James32b0b2f2011-07-13 20:56:50 -0700670 parser.add_option('', '--binhost-conf-dir', dest='binhost_conf_dir',
671 default=_BINHOST_CONF_DIR,
672 help='Directory to commit binhost config with '
673 '--sync-binhost-conf.')
David Jamesfd0b0852011-02-23 11:15:36 -0800674 parser.add_option('-P', '--private', dest='private', action='store_true',
675 default=False, help='Mark gs:// uploads as private.')
David James8ece7ee2011-06-29 16:02:30 -0700676 parser.add_option('', '--skip-upload', dest='skip_upload',
677 action='store_true', default=False,
678 help='Skip upload step.')
David James8fa34ea2011-04-15 13:00:20 -0700679 parser.add_option('', '--upload-board-tarball', dest='upload_board_tarball',
680 action='store_true', default=False,
681 help='Upload board tarball to Google Storage.')
David James27fa7d12011-06-29 17:24:14 -0700682 parser.add_option('', '--debug', dest='debug',
683 action='store_true', default=False,
684 help='Don\'t push or upload prebuilts.')
David James8c846492011-01-25 17:07:29 -0800685
686 options, args = parser.parse_args()
David James8c846492011-01-25 17:07:29 -0800687 if not options.build_path:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700688 Usage(parser, 'Error: you need provide a chroot path')
David James8ece7ee2011-06-29 16:02:30 -0700689 if not options.upload and not options.skip_upload:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700690 Usage(parser, 'Error: you need to provide an upload location using -u')
David James8ece7ee2011-06-29 16:02:30 -0700691 if not options.set_version and options.skip_upload:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700692 Usage(parser, 'Error: If you are using --skip-upload, you must specify a '
David James8ece7ee2011-06-29 16:02:30 -0700693 'version number using --set-version.')
David James9417f272011-05-26 13:24:47 -0700694 if args:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700695 Usage(parser, 'Error: invalid arguments passed to prebuilt.py: %r' % args)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700696
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700697 target = None
698 if options.board:
699 target = BuildTarget(options.board, options.profile)
700
701 if target in options.slave_targets:
702 Usage(parser, 'Error: --board/--profile must not also be a slave target.')
David Jamese2488642011-11-14 16:15:20 -0800703
David James4058b0d2011-12-08 21:24:50 -0800704 if len(set(options.slave_targets)) != len(options.slave_targets):
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700705 Usage(parser, 'Error: --slave-boards must not have duplicates.')
David Jamese2488642011-11-14 16:15:20 -0800706
David James4058b0d2011-12-08 21:24:50 -0800707 if options.slave_targets and options.git_sync:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700708 Usage(parser, 'Error: --slave-boards is not compatible with --git-sync')
David Jamese2488642011-11-14 16:15:20 -0800709
David James8ece7ee2011-06-29 16:02:30 -0700710 if (options.upload_board_tarball and options.skip_upload and
711 options.board == 'amd64-host'):
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700712 Usage(parser, 'Error: --skip-upload is not compatible with '
David James8ece7ee2011-06-29 16:02:30 -0700713 '--upload-board-tarball and --board=amd64-host')
David James8fa34ea2011-04-15 13:00:20 -0700714
David James8ece7ee2011-06-29 16:02:30 -0700715 if (options.upload_board_tarball and not options.skip_upload and
716 not options.upload.startswith('gs://')):
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700717 Usage(parser, 'Error: --upload-board-tarball only works with gs:// URLs.\n'
David James8fa34ea2011-04-15 13:00:20 -0700718 '--upload must be a gs:// URL.')
719
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700720 if options.private:
721 if options.sync_host:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700722 Usage(parser, 'Error: --private and --sync-host/-s cannot be specified '
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700723 'together, we do not support private host prebuilts')
724
David James8ece7ee2011-06-29 16:02:30 -0700725 if not options.upload or not options.upload.startswith('gs://'):
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700726 Usage(parser, 'Error: --private is only valid for gs:// URLs.\n'
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700727 '--upload must be a gs:// URL.')
728
729 if options.binhost_base_url != _BINHOST_BASE_URL:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700730 Usage(parser, 'Error: when using --private the --binhost-base-url '
David Jamese2488642011-11-14 16:15:20 -0800731 'is automatically derived.')
David James27fa7d12011-06-29 17:24:14 -0700732
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700733 return options, target
David Jamesc0f158a2011-02-22 16:07:29 -0800734
735def main():
David Jamesdb401072011-06-10 12:17:16 -0700736 # Set umask to a sane value so that files created as root are readable.
737 os.umask(022)
738
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700739 options, target = ParseOptions()
David Jamesc0f158a2011-02-22 16:07:29 -0800740
David James05bcb2b2011-02-09 09:25:47 -0800741 # Calculate a list of Packages index files to compare against. Whenever we
742 # upload a package, we check to make sure it's not already stored in one of
743 # the packages files we uploaded. This list of packages files might contain
744 # both board and host packages.
David Jamesce093af2011-02-23 15:21:58 -0800745 pkg_indexes = _GrabAllRemotePackageIndexes(options.previous_binhost_url)
David James8c846492011-01-25 17:07:29 -0800746
David James8ece7ee2011-06-29 16:02:30 -0700747 if options.set_version:
748 version = options.set_version
749 else:
750 version = GetVersion()
David Jamesc0f158a2011-02-22 16:07:29 -0800751 if options.prepend_version:
752 version = '%s-%s' % (options.prepend_version, version)
753
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700754 acl = 'public-read'
755 binhost_base_url = options.binhost_base_url
756
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700757 if target and options.private:
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700758 binhost_base_url = options.upload
759 board_path = GetBoardPathFromCrosOverlayList(options.build_path,
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700760 target)
Scott Zawalskiab1bed32011-03-16 15:24:24 -0700761 acl = os.path.join(board_path, _GOOGLESTORAGE_ACL_FILE)
762
763 uploader = PrebuiltUploader(options.upload, acl, binhost_base_url,
David James615e5b52011-06-03 11:10:15 -0700764 pkg_indexes, options.build_path,
David James27fa7d12011-06-29 17:24:14 -0700765 options.packages, options.skip_upload,
David Jamese2488642011-11-14 16:15:20 -0800766 options.binhost_conf_dir, options.debug,
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700767 target, options.slave_targets)
David Jamesc0f158a2011-02-22 16:07:29 -0800768
David James8c846492011-01-25 17:07:29 -0800769 if options.sync_host:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700770 uploader.SyncHostPrebuilts(version, options.key, options.git_sync,
771 options.sync_binhost_conf)
David James8c846492011-01-25 17:07:29 -0800772
Chris Sosa62c8ff52012-06-04 15:03:12 -0700773 if options.board or options.slave_targets:
Chris Sosa6a5dceb2012-05-14 13:48:56 -0700774 uploader.SyncBoardPrebuilts(version, options.key, options.git_sync,
775 options.sync_binhost_conf,
776 options.upload_board_tarball)
David James8c846492011-01-25 17:07:29 -0800777
778if __name__ == '__main__':
779 main()