Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6)

Side by Side Diff: prebuilt.py

Issue 4102001: Add support for prebuilt uploading using rsync. (Closed) Base URL: ssh://git@gitrw.chromium.org:9222/crosutils.git
Patch Set: Fix nits Created 10 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | prebuilt_unittest.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. 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 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 import datetime 6 import datetime
7 import multiprocessing 7 import multiprocessing
8 import optparse 8 import optparse
9 import os 9 import os
10 import re 10 import re
11 import sys 11 import sys
12 import tempfile 12 import tempfile
13 13
14 from chromite.lib import cros_build_lib 14 from chromite.lib import cros_build_lib
15 """ 15 """
16 This script is used to upload host prebuilts as well as board BINHOSTS to 16 This script is used to upload host prebuilts as well as board BINHOSTS.
17 Google Storage. 17
18 If the URL starts with 'gs://', we upload using gsutil to Google Storage.
19 Otherwise, rsync is used.
18 20
19 After a build is successfully uploaded a file is updated with the proper 21 After a build is successfully uploaded a file is updated with the proper
20 BINHOST version as well as the target board. This file is defined in GIT_FILE 22 BINHOST version as well as the target board. This file is defined in GIT_FILE
21 23
22 24
23 To read more about prebuilts/binhost binary packages please refer to: 25 To read more about prebuilts/binhost binary packages please refer to:
24 http://sites/chromeos/for-team-members/engineering/releng/prebuilt-binaries-for- streamlining-the-build-process 26 http://sites/chromeos/for-team-members/engineering/releng/prebuilt-binaries-for- streamlining-the-build-process
25 27
26 28
27 Example of uploading prebuilt amd64 host files 29 Example of uploading prebuilt amd64 host files to Google Storage:
28 ./prebuilt.py -p /b/cbuild/build -s -u gs://chromeos-prebuilt 30 ./prebuilt.py -p /b/cbuild/build -s -u gs://chromeos-prebuilt
29 31
30 Example of uploading x86-dogfood binhosts 32 Example of uploading x86-dogfood binhosts to Google Storage:
31 ./prebuilt.py -b x86-dogfood -p /b/cbuild/build/ -u gs://chromeos-prebuilt -g 33 ./prebuilt.py -b x86-dogfood -p /b/cbuild/build/ -u gs://chromeos-prebuilt -g
34
35 Example of uploading prebuilt amd64 host files using rsync:
36 ./prebuilt.py -p /b/cbuild/build -s -u codf30.jail:/tmp
32 """ 37 """
33 38
34 # as per http://crosbug.com/5855 always filter the below packages 39 # as per http://crosbug.com/5855 always filter the below packages
35 _FILTER_PACKAGES = set() 40 _FILTER_PACKAGES = set()
36 _RETRIES = 3 41 _RETRIES = 3
37 _GSUTIL_BIN = '/b/third_party/gsutil/gsutil' 42 _GSUTIL_BIN = '/b/third_party/gsutil/gsutil'
38 _HOST_PACKAGES_PATH = 'chroot/var/lib/portage/pkgs' 43 _HOST_PACKAGES_PATH = 'chroot/var/lib/portage/pkgs'
39 _HOST_TARGET = 'amd64' 44 _HOST_TARGET = 'amd64'
40 _BOARD_PATH = 'chroot/build/%(board)s' 45 _BOARD_PATH = 'chroot/build/%(board)s'
41 _BOTO_CONFIG = '/home/chrome-bot/external-boto' 46 _BOTO_CONFIG = '/home/chrome-bot/external-boto'
42 # board/board-target/version' 47 # board/board-target/version/packages/'
43 _GS_BOARD_PATH = 'board/%(board)s/%(version)s/' 48 _REL_BOARD_PATH = 'board/%(board)s/%(version)s/packages'
44 # We only support amd64 right now 49 # host/host-target/version/packages/'
45 _GS_HOST_PATH = 'host/%s' % _HOST_TARGET 50 _REL_HOST_PATH = 'host/%(target)s/%(version)s/packages'
46 # Private overlays to look at for builds to filter 51 # Private overlays to look at for builds to filter
47 # relative to build path 52 # relative to build path
48 _PRIVATE_OVERLAY_DIR = 'src/private-overlays' 53 _PRIVATE_OVERLAY_DIR = 'src/private-overlays'
49 _BINHOST_BASE_DIR = 'src/overlays' 54 _BINHOST_BASE_DIR = 'src/overlays'
50 #_BINHOST_BASE_URL = 'http://commondatastorage.googleapis.com/chromeos-prebuilt' 55 #_BINHOST_BASE_URL = 'http://commondatastorage.googleapis.com/chromeos-prebuilt'
51 _BINHOST_BASE_URL = 'http://gsdview.appspot.com/chromeos-prebuilt' 56 _BINHOST_BASE_URL = 'http://gsdview.appspot.com/chromeos-prebuilt'
52 _PREBUILT_BASE_DIR = 'src/third_party/chromiumos-overlay/chromeos/config/' 57 _PREBUILT_BASE_DIR = 'src/third_party/chromiumos-overlay/chromeos/config/'
53 # Created in the event of new host targets becoming available 58 # Created in the event of new host targets becoming available
54 _PREBUILT_MAKE_CONF = {'amd64': os.path.join(_PREBUILT_BASE_DIR, 59 _PREBUILT_MAKE_CONF = {'amd64': os.path.join(_PREBUILT_BASE_DIR,
55 'make.conf.amd64-host')} 60 'make.conf.amd64-host')}
(...skipping 186 matching lines...) Expand 10 before | Expand all | Expand 10 after
242 filtered_packages.write("".join(section)) 247 filtered_packages.write("".join(section))
243 packages_file.close() 248 packages_file.close()
244 249
245 # Flush contents to disk. 250 # Flush contents to disk.
246 filtered_packages.flush() 251 filtered_packages.flush()
247 filtered_packages.seek(0) 252 filtered_packages.seek(0)
248 253
249 return filtered_packages 254 return filtered_packages
250 255
251 256
257 def _RetryRun(cmd, print_cmd=True, shell=False):
258 """Run the specified command, retrying if necessary.
259
260 Args:
261 cmd: The command to run.
262 print_cmd: Whether to print out the cmd.
263 shell: Whether to treat the command as a shell.
264
265 Returns:
266 True if the command succeeded. Otherwise, returns False.
267 """
268
269 # TODO(scottz): port to use _Run or similar when it is available in
270 # cros_build_lib.
271 for attempt in range(_RETRIES):
272 try:
273 output = cros_build_lib.RunCommand(cmd, print_cmd=print_cmd, shell=shell)
274 return True
275 except cros_build_lib.RunCommandError:
276 print 'Failed to run %s' % cmd
277 else:
278 print 'Retry failed run %s, giving up' % cmd
279 return False
280
281
252 def _GsUpload(args): 282 def _GsUpload(args):
253 """Upload to GS bucket. 283 """Upload to GS bucket.
254 284
255 Args: 285 Args:
256 args: a tuple of two arguments that contains local_file and remote_file. 286 args: a tuple of two arguments that contains local_file and remote_file.
257 287
258 Returns: 288 Returns:
259 Return the arg tuple of two if the upload failed 289 Return the arg tuple of two if the upload failed
260 """ 290 """
261 (local_file, remote_file) = args 291 (local_file, remote_file) = args
262 if ShouldFilterPackage(local_file): 292 if ShouldFilterPackage(local_file):
263 return 293 return
264 294
265 if local_file.endswith("/Packages"): 295 if local_file.endswith("/Packages"):
266 filtered_packages_file = FilterPackagesFile(local_file) 296 filtered_packages_file = FilterPackagesFile(local_file)
267 local_file = filtered_packages_file.name 297 local_file = filtered_packages_file.name
268 298
269 cmd = '%s cp -a public-read %s %s' % (_GSUTIL_BIN, local_file, remote_file) 299 cmd = '%s cp -a public-read %s %s' % (_GSUTIL_BIN, local_file, remote_file)
270 # TODO(scottz): port to use _Run or similar when it is available in 300 if not _RetryRun(cmd, print_cmd=False, shell=True):
271 # cros_build_lib. 301 return (local_file, remote_file)
272 for attempt in range(_RETRIES):
273 try:
274 output = cros_build_lib.RunCommand(cmd, print_cmd=False, shell=True)
275 break
276 except cros_build_lib.RunCommandError:
277 print 'Failed to sync %s -> %s, retrying' % (local_file, remote_file)
278 else:
279 # TODO(scottz): potentially return what failed so we can do something with
280 # with it but for now just print an error.
281 print 'Retry failed uploading %s -> %s, giving up' % (local_file,
282 remote_file)
283 return args
284 302
285 303
286 def RemoteUpload(files, pool=10): 304 def RemoteUpload(files, pool=10):
287 """Upload to google storage. 305 """Upload to google storage.
288 306
289 Create a pool of process and call _GsUpload with the proper arguments. 307 Create a pool of process and call _GsUpload with the proper arguments.
290 308
291 Args: 309 Args:
292 files: dictionary with keys to local files and values to remote path. 310 files: dictionary with keys to local files and values to remote path.
293 pool: integer of maximum proesses to have at the same time. 311 pool: integer of maximum proesses to have at the same time.
294 312
295 Returns: 313 Returns:
296 Return a set of tuple arguments of the failed uploads 314 Return a set of tuple arguments of the failed uploads
297 """ 315 """
298 # TODO(scottz) port this to use _RunManyParallel when it is available in 316 # TODO(scottz) port this to use _RunManyParallel when it is available in
299 # cros_build_lib 317 # cros_build_lib
300 pool = multiprocessing.Pool(processes=pool) 318 pool = multiprocessing.Pool(processes=pool)
301 workers = [] 319 workers = []
302 for local_file, remote_path in files.iteritems(): 320 for local_file, remote_path in files.iteritems():
303 workers.append((local_file, remote_path)) 321 workers.append((local_file, remote_path))
304 322
305 result = pool.map_async(_GsUpload, workers, chunksize=1) 323 result = pool.map_async(_GsUpload, workers, chunksize=1)
306 while True: 324 while True:
307 try: 325 try:
308 return set(result.get(60*60)) 326 return set(result.get(60*60))
309 except multiprocessing.TimeoutError: 327 except multiprocessing.TimeoutError:
310 pass 328 pass
311 329
312 330
313 def GenerateUploadDict(local_path, gs_path, strip_str): 331 def GenerateUploadDict(local_path, gs_path):
314 """Build a dictionary of local remote file key pairs for gsutil to upload. 332 """Build a dictionary of local remote file key pairs for gsutil to upload.
315 333
316 Args: 334 Args:
317 local_path: A path to the file on the local hard drive. 335 local_path: A path to the file on the local hard drive.
318 gs_path: Path to upload in Google Storage. 336 gs_path: Path to upload in Google Storage.
319 strip_str: String to remove from the local_path so that the relative
320 file path can be tacked on to the gs_path.
321 337
322 Returns: 338 Returns:
323 Returns a dictionary of file path/gs_dest_path pairs 339 Returns a dictionary of file path/gs_dest_path pairs
324 """ 340 """
325 files_to_sync = cros_build_lib.ListFiles(local_path) 341 files_to_sync = cros_build_lib.ListFiles(local_path)
326 upload_files = {} 342 upload_files = {}
327 for file_path in files_to_sync: 343 for file_path in files_to_sync:
328 filename = file_path.replace(strip_str, '').lstrip('/') 344 filename = file_path.replace(local_path, '').lstrip('/')
329 gs_file_path = os.path.join(gs_path, filename) 345 gs_file_path = os.path.join(gs_path, filename)
330 upload_files[file_path] = gs_file_path 346 upload_files[file_path] = gs_file_path
331 347
332 return upload_files 348 return upload_files
333 349
334 350
335 def DetermineMakeConfFile(target): 351 def DetermineMakeConfFile(target):
336 """Determine the make.conf file that needs to be updated for prebuilts. 352 """Determine the make.conf file that needs to be updated for prebuilts.
337 353
338 Args: 354 Args:
(...skipping 15 matching lines...) Expand all
354 make_path = os.path.join(_BINHOST_BASE_DIR, overlay_str, 'make.conf') 370 make_path = os.path.join(_BINHOST_BASE_DIR, overlay_str, 'make.conf')
355 elif re.match('.*?-\w+', target): 371 elif re.match('.*?-\w+', target):
356 overlay_str = 'overlay-%s' % target 372 overlay_str = 'overlay-%s' % target
357 make_path = os.path.join(_BINHOST_BASE_DIR, overlay_str, 'make.conf') 373 make_path = os.path.join(_BINHOST_BASE_DIR, overlay_str, 'make.conf')
358 else: 374 else:
359 raise UnknownBoardFormat('Unknown format: %s' % target) 375 raise UnknownBoardFormat('Unknown format: %s' % target)
360 376
361 return os.path.join(make_path) 377 return os.path.join(make_path)
362 378
363 379
364 def UploadPrebuilt(build_path, bucket, version, board=None, git_sync=False): 380 def UploadPrebuilt(build_path, upload_location, version, binhost_base_url,
381 board=None, git_sync=False):
365 """Upload Host prebuilt files to Google Storage space. 382 """Upload Host prebuilt files to Google Storage space.
366 383
367 Args: 384 Args:
368 build_path: The path to the root of the chroot. 385 build_path: The path to the root of the chroot.
369 bucket: The Google Storage bucket to upload to. 386 upload_location: The upload location.
370 board: The board to upload to Google Storage, if this is None upload 387 board: The board to upload to Google Storage, if this is None upload
371 host packages. 388 host packages.
372 git_sync: If set, update make.conf of target to reference the latest 389 git_sync: If set, update make.conf of target to reference the latest
373 prebuilt packages genereated here. 390 prebuilt packages genereated here.
374 """ 391 """
375 392
376 if not board: 393 if not board:
377 # We are uploading host packages 394 # We are uploading host packages
378 # TODO(scottz): eventually add support for different host_targets 395 # TODO(scottz): eventually add support for different host_targets
379 package_path = os.path.join(build_path, _HOST_PACKAGES_PATH) 396 package_path = os.path.join(build_path, _HOST_PACKAGES_PATH)
380 gs_path = os.path.join(bucket, _GS_HOST_PATH, version) 397 url_suffix = _REL_HOST_PATH % {'version': version, 'target': _HOST_TARGET}
381 strip_pattern = package_path
382 package_string = _HOST_TARGET 398 package_string = _HOST_TARGET
383 git_file = os.path.join(build_path, _PREBUILT_MAKE_CONF[_HOST_TARGET]) 399 git_file = os.path.join(build_path, _PREBUILT_MAKE_CONF[_HOST_TARGET])
384 url_suffix = '%s/%s/' % (_GS_HOST_PATH, version)
385 else: 400 else:
386 board_path = os.path.join(build_path, _BOARD_PATH % {'board': board}) 401 board_path = os.path.join(build_path, _BOARD_PATH % {'board': board})
387 package_path = os.path.join(board_path, 'packages') 402 package_path = os.path.join(board_path, 'packages')
388 package_string = board 403 package_string = board
389 strip_pattern = board_path 404 url_suffix = _REL_BOARD_PATH % {'board': board, 'version': version}
390 remote_board_path = _GS_BOARD_PATH % {'board': board, 'version': version}
391 gs_path = os.path.join(bucket, remote_board_path)
392 git_file = os.path.join(build_path, DetermineMakeConfFile(board)) 405 git_file = os.path.join(build_path, DetermineMakeConfFile(board))
393 url_suffix = remote_board_path 406 remote_location = os.path.join(upload_location, url_suffix)
394 407
395 upload_files = GenerateUploadDict(package_path, gs_path, strip_pattern) 408 if upload_location.startswith('gs://'):
409 upload_files = GenerateUploadDict(package_path, remote_location)
396 410
397 print 'Uploading %s' % package_string 411 print 'Uploading %s' % package_string
398 failed_uploads = RemoteUpload(upload_files) 412 failed_uploads = RemoteUpload(upload_files)
399 if len(failed_uploads) > 1 or (None not in failed_uploads): 413 if len(failed_uploads) > 1 or (None not in failed_uploads):
400 error_msg = ['%s -> %s\n' % args for args in failed_uploads] 414 error_msg = ['%s -> %s\n' % args for args in failed_uploads]
401 raise UploadFailed('Error uploading:\n%s' % error_msg) 415 raise UploadFailed('Error uploading:\n%s' % error_msg)
416 else:
417 ssh_server, remote_path = remote_location.split(':', 1)
418 cmds = ['ssh %s mkdir -p %s' % (ssh_server, remote_path),
419 'rsync -av %s/ %s/' % (package_path, remote_location)]
420 for cmd in cmds:
421 if not _RetryRun(cmd, shell=True):
422 raise UploadFailed('Could not run %s' % cmd)
402 423
403 if git_sync: 424 if git_sync:
404 url_value = '%s/%s' % (_BINHOST_BASE_URL, url_suffix) 425 url_value = '%s/%s/' % (binhost_base_url, url_suffix)
405 RevGitFile(git_file, url_value) 426 RevGitFile(git_file, url_value)
406 427
407 428
408 def usage(parser, msg): 429 def usage(parser, msg):
409 """Display usage message and parser help then exit with 1.""" 430 """Display usage message and parser help then exit with 1."""
410 print >> sys.stderr, msg 431 print >> sys.stderr, msg
411 parser.print_help() 432 parser.print_help()
412 sys.exit(1) 433 sys.exit(1)
413 434
414 435
415 def main(): 436 def main():
416 parser = optparse.OptionParser() 437 parser = optparse.OptionParser()
438 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
439 default=_BINHOST_BASE_URL,
440 help='Base URL to use for binhost in make.conf updates')
417 parser.add_option('-b', '--board', dest='board', default=None, 441 parser.add_option('-b', '--board', dest='board', default=None,
418 help='Board type that was built on this machine') 442 help='Board type that was built on this machine')
419 parser.add_option('-p', '--build-path', dest='build_path', 443 parser.add_option('-p', '--build-path', dest='build_path',
420 help='Path to the chroot') 444 help='Path to the chroot')
421 parser.add_option('-s', '--sync-host', dest='sync_host', 445 parser.add_option('-s', '--sync-host', dest='sync_host',
422 default=False, action='store_true', 446 default=False, action='store_true',
423 help='Sync host prebuilts') 447 help='Sync host prebuilts')
424 parser.add_option('-g', '--git-sync', dest='git_sync', 448 parser.add_option('-g', '--git-sync', dest='git_sync',
425 default=False, action='store_true', 449 default=False, action='store_true',
426 help='Enable git version sync (This commits to a repo)') 450 help='Enable git version sync (This commits to a repo)')
427 parser.add_option('-u', '--upload', dest='upload', 451 parser.add_option('-u', '--upload', dest='upload',
428 default=None, 452 default=None,
429 help='Upload to GS bucket') 453 help='Upload location')
430 parser.add_option('-V', '--prepend-version', dest='prepend_version', 454 parser.add_option('-V', '--prepend-version', dest='prepend_version',
431 default=None, 455 default=None,
432 help='Add an identifier to the front of the version') 456 help='Add an identifier to the front of the version')
433 parser.add_option('-f', '--filters', dest='filters', action='store_true', 457 parser.add_option('-f', '--filters', dest='filters', action='store_true',
434 default=False, 458 default=False,
435 help='Turn on filtering of private ebuild packages') 459 help='Turn on filtering of private ebuild packages')
436 460
437 options, args = parser.parse_args() 461 options, args = parser.parse_args()
438 # Setup boto environment for gsutil to use 462 # Setup boto environment for gsutil to use
439 os.environ['BOTO_CONFIG'] = _BOTO_CONFIG 463 os.environ['BOTO_CONFIG'] = _BOTO_CONFIG
440 if not options.build_path: 464 if not options.build_path:
441 usage(parser, 'Error: you need provide a chroot path') 465 usage(parser, 'Error: you need provide a chroot path')
442 466
443 if not options.upload: 467 if not options.upload:
444 usage(parser, 'Error: you need to provide a gsutil upload bucket -u') 468 usage(parser, 'Error: you need to provide an upload location using -u')
445 469
446 if options.filters: 470 if options.filters:
471 # TODO(davidjames): It might be nice to be able to filter private ebuilds
472 # from rsync uploads as well, some day. But for now it's not needed.
473 if not options.upload.startswith("gs://"):
474 usage(parser, 'Error: filtering only works with gs:// paths')
447 LoadPrivateFilters(options.build_path) 475 LoadPrivateFilters(options.build_path)
448 476
449 version = GetVersion() 477 version = GetVersion()
450 if options.prepend_version: 478 if options.prepend_version:
451 version = '%s-%s' % (options.prepend_version, version) 479 version = '%s-%s' % (options.prepend_version, version)
452 480
453 if options.sync_host: 481 if options.sync_host:
454 UploadPrebuilt(options.build_path, options.upload, version, 482 UploadPrebuilt(options.build_path, options.upload, version,
455 git_sync=options.git_sync) 483 options.binhost_base_url, git_sync=options.git_sync)
456 484
457 if options.board: 485 if options.board:
458 UploadPrebuilt(options.build_path, options.upload, version, 486 UploadPrebuilt(options.build_path, options.upload, version,
459 board=options.board, git_sync=options.git_sync) 487 options.binhost_base_url, board=options.board,
488 git_sync=options.git_sync)
460 489
461 490
462 if __name__ == '__main__': 491 if __name__ == '__main__':
463 main() 492 main()
OLDNEW
« no previous file with comments | « no previous file | prebuilt_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698