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

Side by Side Diff: prebuilt.py

Issue 5344002: Update cbuildbot.py and prebuilt.py to deduplicate preflight prebuilts. (Closed) Base URL: None@preflight_upload
Patch Set: Address comments by dianders. 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
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 import time 13 import time
14 import urlparse
14 15
15 from chromite.lib import cros_build_lib 16 from chromite.lib import cros_build_lib
17 from chromite.lib.binpkg import (GrabLocalPackageIndex, GrabRemotePackageIndex,
18 PackageIndex)
16 """ 19 """
17 This script is used to upload host prebuilts as well as board BINHOSTS. 20 This script is used to upload host prebuilts as well as board BINHOSTS.
18 21
19 If the URL starts with 'gs://', we upload using gsutil to Google Storage. 22 If the URL starts with 'gs://', we upload using gsutil to Google Storage.
20 Otherwise, rsync is used. 23 Otherwise, rsync is used.
21 24
22 After a build is successfully uploaded a file is updated with the proper 25 After a build is successfully uploaded a file is updated with the proper
23 BINHOST version as well as the target board. This file is defined in GIT_FILE 26 BINHOST version as well as the target board. This file is defined in GIT_FILE
24 27
25 28
(...skipping 195 matching lines...) Expand 10 before | Expand all | Expand 10 after
221 False otherwise. 224 False otherwise.
222 """ 225 """
223 for name in _FILTER_PACKAGES: 226 for name in _FILTER_PACKAGES:
224 if name in file_path: 227 if name in file_path:
225 print 'FILTERING %s' % file_path 228 print 'FILTERING %s' % file_path
226 return True 229 return True
227 230
228 return False 231 return False
229 232
230 233
231 def _ShouldFilterPackageFileSection(section): 234 def _RetryRun(cmd, print_cmd=True, shell=False, cwd=None):
232 """Return whether an section in the package file should be filtered out.
233
234 Args:
235 section: The section, as a list of strings.
236
237 Returns:
238 True if the section should be excluded.
239 """
240
241 for line in section:
242 if line.startswith("CPV: "):
243 package = line.replace("CPV: ", "").rstrip()
244 if ShouldFilterPackage(package):
245 return True
246 else:
247 return False
248
249
250 def FilterPackagesFile(packages_filename):
251 """Read a portage Packages file and filter out private packages.
252
253 The new, filtered packages file is written to a temporary file.
254
255 Args:
256 packages_filename: The filename of the Packages file.
257
258 Returns:
259 filtered_packages: A filtered Packages file, as a NamedTemporaryFile.
260 """
261
262 packages_file = open(packages_filename)
263 filtered_packages = tempfile.NamedTemporaryFile()
264 section = []
265 for line in packages_file:
266 if line == "\n":
267 if not _ShouldFilterPackageFileSection(section):
268 # Looks like this section doesn't contain a private package. Write it
269 # out.
270 filtered_packages.write("".join(section))
271
272 # Start next section.
273 section = []
274
275 section.append(line)
276 else:
277 if not _ShouldFilterPackageFileSection(section):
278 filtered_packages.write("".join(section))
279 packages_file.close()
280
281 # Flush contents to disk.
282 filtered_packages.flush()
283 filtered_packages.seek(0)
284
285 return filtered_packages
286
287
288 def _RetryRun(cmd, print_cmd=True, shell=False):
289 """Run the specified command, retrying if necessary. 235 """Run the specified command, retrying if necessary.
290 236
291 Args: 237 Args:
292 cmd: The command to run. 238 cmd: The command to run.
293 print_cmd: Whether to print out the cmd. 239 print_cmd: Whether to print out the cmd.
294 shell: Whether to treat the command as a shell. 240 shell: Whether to treat the command as a shell.
241 cwd: Working directory to run command in.
295 242
296 Returns: 243 Returns:
297 True if the command succeeded. Otherwise, returns False. 244 True if the command succeeded. Otherwise, returns False.
298 """ 245 """
299 246
300 # TODO(scottz): port to use _Run or similar when it is available in 247 # TODO(scottz): port to use _Run or similar when it is available in
301 # cros_build_lib. 248 # cros_build_lib.
302 for attempt in range(_RETRIES): 249 for attempt in range(_RETRIES):
303 try: 250 try:
304 output = cros_build_lib.RunCommand(cmd, print_cmd=print_cmd, shell=shell) 251 output = cros_build_lib.RunCommand(cmd, print_cmd=print_cmd, shell=shell,
252 cwd=cwd)
305 return True 253 return True
306 except cros_build_lib.RunCommandError: 254 except cros_build_lib.RunCommandError:
307 print 'Failed to run %s' % cmd 255 print 'Failed to run %s' % cmd
308 else: 256 else:
309 print 'Retry failed run %s, giving up' % cmd 257 print 'Retry failed run %s, giving up' % cmd
310 return False 258 return False
311 259
312 260
313 def _GsUpload(args): 261 def _GsUpload(args):
314 """Upload to GS bucket. 262 """Upload to GS bucket.
315 263
316 Args: 264 Args:
317 args: a tuple of two arguments that contains local_file and remote_file. 265 args: a tuple of two arguments that contains local_file and remote_file.
318 266
319 Returns: 267 Returns:
320 Return the arg tuple of two if the upload failed 268 Return the arg tuple of two if the upload failed
321 """ 269 """
322 (local_file, remote_file) = args 270 (local_file, remote_file) = args
323 if ShouldFilterPackage(local_file):
324 return
325
326 if local_file.endswith("/Packages"):
327 filtered_packages_file = FilterPackagesFile(local_file)
328 local_file = filtered_packages_file.name
329 271
330 cmd = '%s cp -a public-read %s %s' % (_GSUTIL_BIN, local_file, remote_file) 272 cmd = '%s cp -a public-read %s %s' % (_GSUTIL_BIN, local_file, remote_file)
331 if not _RetryRun(cmd, print_cmd=False, shell=True): 273 if not _RetryRun(cmd, print_cmd=False, shell=True):
332 return (local_file, remote_file) 274 return (local_file, remote_file)
333 275
334 276
335 def RemoteUpload(files, pool=10): 277 def RemoteUpload(files, pool=10):
336 """Upload to google storage. 278 """Upload to google storage.
337 279
338 Create a pool of process and call _GsUpload with the proper arguments. 280 Create a pool of process and call _GsUpload with the proper arguments.
(...skipping 13 matching lines...) Expand all
352 workers.append((local_file, remote_path)) 294 workers.append((local_file, remote_path))
353 295
354 result = pool.map_async(_GsUpload, workers, chunksize=1) 296 result = pool.map_async(_GsUpload, workers, chunksize=1)
355 while True: 297 while True:
356 try: 298 try:
357 return set(result.get(60*60)) 299 return set(result.get(60*60))
358 except multiprocessing.TimeoutError: 300 except multiprocessing.TimeoutError:
359 pass 301 pass
360 302
361 303
362 def GenerateUploadDict(local_path, gs_path): 304 def GenerateUploadDict(base_local_path, base_remote_path, pkgs):
363 """Build a dictionary of local remote file key pairs for gsutil to upload. 305 """Build a dictionary of local remote file key pairs to upload.
364 306
365 Args: 307 Args:
366 local_path: A path to the file on the local hard drive. 308 base_local_path: The base path to the files on the local hard drive.
367 gs_path: Path to upload in Google Storage. 309 remote_path: The base path to the remote paths.
diandersAtChromium 2010/11/25 00:45:37 remote_path ==> base_remote_path
310 pkgs: The packages to upload.
diandersAtChromium 2010/11/25 00:45:37 (a list of dictionaries, one per package).
368 311
369 Returns: 312 Returns:
370 Returns a dictionary of file path/gs_dest_path pairs 313 Returns a dictionary of local_path/remote_path pairs
371 """ 314 """
372 files_to_sync = cros_build_lib.ListFiles(local_path)
373 upload_files = {} 315 upload_files = {}
374 for file_path in files_to_sync: 316 for pkg in pkgs:
375 filename = file_path.replace(local_path, '').lstrip('/') 317 suffix = pkg['CPV'] + '.tbz2'
376 gs_file_path = os.path.join(gs_path, filename) 318 local_path = os.path.join(base_local_path, suffix)
377 upload_files[file_path] = gs_file_path 319 assert os.path.exists(local_path)
320 remote_path = urlparse.urljoin(base_remote_path, suffix)
321 upload_files[local_path] = remote_path
378 322
379 return upload_files 323 return upload_files
380 324
381 325
382 def DetermineMakeConfFile(target): 326 def DetermineMakeConfFile(target):
383 """Determine the make.conf file that needs to be updated for prebuilts. 327 """Determine the make.conf file that needs to be updated for prebuilts.
384 328
385 Args: 329 Args:
386 target: String representation of the board. This includes host and board 330 target: String representation of the board. This includes host and board
387 targets 331 targets
(...skipping 15 matching lines...) Expand all
403 overlay_str = 'overlay-%s' % target 347 overlay_str = 'overlay-%s' % target
404 make_path = os.path.join(_BINHOST_BASE_DIR, overlay_str, 'make.conf') 348 make_path = os.path.join(_BINHOST_BASE_DIR, overlay_str, 'make.conf')
405 else: 349 else:
406 raise UnknownBoardFormat('Unknown format: %s' % target) 350 raise UnknownBoardFormat('Unknown format: %s' % target)
407 351
408 return os.path.join(make_path) 352 return os.path.join(make_path)
409 353
410 354
411 def UploadPrebuilt(build_path, upload_location, version, binhost_base_url, 355 def UploadPrebuilt(build_path, upload_location, version, binhost_base_url,
412 board=None, git_sync=False, git_sync_retries=5, 356 board=None, git_sync=False, git_sync_retries=5,
413 key='PORTAGE_BINHOST', sync_binhost_conf=False): 357 key='PORTAGE_BINHOST', pkgindexes=[],
358 sync_binhost_conf=False):
diandersAtChromium 2010/11/25 00:45:37 I worry a little bit about adding a parameter anyw
414 """Upload Host prebuilt files to Google Storage space. 359 """Upload Host prebuilt files to Google Storage space.
diandersAtChromium 2010/11/25 00:45:37 Not only to Google Storage space. Also supports s
415 360
416 Args: 361 Args:
417 build_path: The path to the root of the chroot. 362 build_path: The path to the root of the chroot.
418 upload_location: The upload location. 363 upload_location: The upload location.
diandersAtChromium 2010/11/25 00:45:37 Missing desc for version and binhost_base_url.
diandersAtChromium 2010/11/25 00:45:37 upload_location: Can be gs:// style URL, or just h
419 board: The board to upload to Google Storage, if this is None upload 364 board: The board to upload to Google Storage. If this is None, upload
420 host packages. 365 host packages.
421 git_sync: If set, update make.conf of target to reference the latest 366 git_sync: If set, update make.conf of target to reference the latest
422 prebuilt packages generated here. 367 prebuilt packages generated here.
423 git_sync_retries: How many times to retry pushing when updating git files. 368 git_sync_retries: How many times to retry pushing when updating git files.
424 This helps avoid failures when multiple bots are modifying the same Repo. 369 This helps avoid failures when multiple bots are modifying the same Repo.
425 default: 5 370 default: 5
426 key: The variable key to update in the git file. (Default: PORTAGE_BINHOST) 371 key: The variable key to update in the git file. (Default: PORTAGE_BINHOST)
372 pkgindexes: Old uploaded prebuilts to compare against. Instead of
373 uploading duplicate files, we just link to the old files.
diandersAtChromium 2010/11/25 00:45:37 Comment: Each of these should be a PackageIndex ob
427 sync_binhost_conf: If set, update binhost config file in chromiumos-overlay 374 sync_binhost_conf: If set, update binhost config file in chromiumos-overlay
428 for the current board or host. 375 for the current board or host.
429 """ 376 """
430 377
431 if not board: 378 if not board:
432 # We are uploading host packages 379 # We are uploading host packages
433 # TODO(scottz): eventually add support for different host_targets 380 # TODO(scottz): eventually add support for different host_targets
434 package_path = os.path.join(build_path, _HOST_PACKAGES_PATH) 381 package_path = os.path.join(build_path, _HOST_PACKAGES_PATH)
435 url_suffix = _REL_HOST_PATH % {'version': version, 'target': _HOST_TARGET} 382 url_suffix = _REL_HOST_PATH % {'version': version, 'target': _HOST_TARGET}
436 package_string = _HOST_TARGET 383 package_string = _HOST_TARGET
437 git_file = os.path.join(build_path, _PREBUILT_MAKE_CONF[_HOST_TARGET]) 384 git_file = os.path.join(build_path, _PREBUILT_MAKE_CONF[_HOST_TARGET])
438 binhost_conf = os.path.join(build_path, 385 binhost_conf = os.path.join(build_path,
439 '%s/host/%s.conf' % (_BINHOST_CONF_DIR, _HOST_TARGET)) 386 '%s/host/%s.conf' % (_BINHOST_CONF_DIR, _HOST_TARGET))
440 else: 387 else:
441 board_path = os.path.join(build_path, _BOARD_PATH % {'board': board}) 388 board_path = os.path.join(build_path, _BOARD_PATH % {'board': board})
442 package_path = os.path.join(board_path, 'packages') 389 package_path = os.path.join(board_path, 'packages')
443 package_string = board 390 package_string = board
444 url_suffix = _REL_BOARD_PATH % {'board': board, 'version': version} 391 url_suffix = _REL_BOARD_PATH % {'board': board, 'version': version}
445 git_file = os.path.join(build_path, DetermineMakeConfFile(board)) 392 git_file = os.path.join(build_path, DetermineMakeConfFile(board))
446 binhost_conf = os.path.join(build_path, 393 binhost_conf = os.path.join(build_path,
447 '%s/target/%s.conf' % (_BINHOST_CONF_DIR, board)) 394 '%s/target/%s.conf' % (_BINHOST_CONF_DIR, board))
448 remote_location = os.path.join(upload_location, url_suffix) 395 remote_location = urlparse.urljoin(upload_location, url_suffix)
396
397 # Process Packages file, removing duplicates and filtered packages.
398 pkgindex = GrabLocalPackageIndex(package_path)
399 pkgindex.SetUploadLocation(binhost_base_url, url_suffix)
400 pkgindex.RemoveFilteredPackages(lambda pkg: ShouldFilterPackage(pkg))
diandersAtChromium 2010/11/25 00:45:37 Any reason not to just pass in ShouldFilterPackage
401 uploads = pkgindex.ResolveDuplicateUploads(pkgindexes)
402
403 # Write Packages file.
404 tmp_packages_file = pkgindex.WriteToNamedTemporaryFile()
449 405
450 if upload_location.startswith('gs://'): 406 if upload_location.startswith('gs://'):
451 upload_files = GenerateUploadDict(package_path, remote_location) 407 # Build list of files to upload.
408 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
409 remote_file = urlparse.urljoin(remote_location, 'Packages')
410 upload_files[tmp_packages_file.name] = remote_file
452 411
453 print 'Uploading %s' % package_string 412 print 'Uploading %s' % package_string
454 failed_uploads = RemoteUpload(upload_files) 413 failed_uploads = RemoteUpload(upload_files)
455 if len(failed_uploads) > 1 or (None not in failed_uploads): 414 if len(failed_uploads) > 1 or (None not in failed_uploads):
456 error_msg = ['%s -> %s\n' % args for args in failed_uploads] 415 error_msg = ['%s -> %s\n' % args for args in failed_uploads]
457 raise UploadFailed('Error uploading:\n%s' % error_msg) 416 raise UploadFailed('Error uploading:\n%s' % error_msg)
458 else: 417 else:
418 pkgs = ' '.join(p['CPV'] + '.tbz2' for p in uploads)
459 ssh_server, remote_path = remote_location.split(':', 1) 419 ssh_server, remote_path = remote_location.split(':', 1)
460 cmds = ['ssh %s mkdir -p %s' % (ssh_server, remote_path), 420 d = { 'pkgindex': tmp_packages_file.name,
diandersAtChromium 2010/11/25 00:45:37 Technically, maybe should have os.path.abspath() o
461 'rsync -av %s/ %s/' % (package_path, remote_location)] 421 'pkgs': pkgs,
422 'remote_path': remote_path,
423 'remote_location': remote_location,
424 'ssh_server': ssh_server }
425 cmds = ['ssh %(ssh_server)s mkdir -p %(remote_path)s' % d,
426 'rsync -av %(pkgindex)s %(remote_location)s/Packages' % d]
427 if pkgs:
428 cmds.append('rsync -Rav %(pkgs)s %(remote_location)s/' % d)
462 for cmd in cmds: 429 for cmd in cmds:
463 if not _RetryRun(cmd, shell=True): 430 if not _RetryRun(cmd, shell=True, cwd=package_path):
464 raise UploadFailed('Could not run %s' % cmd) 431 raise UploadFailed('Could not run %s' % cmd)
465 432
466 url_value = '%s/%s/' % (binhost_base_url, url_suffix) 433 url_value = '%s/%s/' % (binhost_base_url, url_suffix)
467 434
468 if git_sync: 435 if git_sync:
469 RevGitFile(git_file, url_value, retries=git_sync_retries, key=key) 436 RevGitFile(git_file, url_value, retries=git_sync_retries, key=key)
470 437
471 if sync_binhost_conf: 438 if sync_binhost_conf:
472 binhost_dir = os.path.dirname(os.path.abspath(binhost_conf)) 439 binhost_dir = os.path.dirname(os.path.abspath(binhost_conf))
473 binhost_filename = os.path.basename(binhost_conf) 440 binhost_filename = os.path.basename(binhost_conf)
(...skipping 16 matching lines...) Expand all
490 print >> sys.stderr, msg 457 print >> sys.stderr, msg
491 parser.print_help() 458 parser.print_help()
492 sys.exit(1) 459 sys.exit(1)
493 460
494 461
495 def main(): 462 def main():
496 parser = optparse.OptionParser() 463 parser = optparse.OptionParser()
497 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url', 464 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
498 default=_BINHOST_BASE_URL, 465 default=_BINHOST_BASE_URL,
499 help='Base URL to use for binhost in make.conf updates') 466 help='Base URL to use for binhost in make.conf updates')
467 parser.add_option('', '--previous-binhost-url', action='append',
468 default=[], dest='previous_binhost_url',
469 help='Previous binhost URL')
diandersAtChromium 2010/11/25 00:45:37 Indicate in help that this option can be specified
500 parser.add_option('-b', '--board', dest='board', default=None, 470 parser.add_option('-b', '--board', dest='board', default=None,
501 help='Board type that was built on this machine') 471 help='Board type that was built on this machine')
502 parser.add_option('-p', '--build-path', dest='build_path', 472 parser.add_option('-p', '--build-path', dest='build_path',
503 help='Path to the chroot') 473 help='Path to the chroot')
504 parser.add_option('-s', '--sync-host', dest='sync_host', 474 parser.add_option('-s', '--sync-host', dest='sync_host',
505 default=False, action='store_true', 475 default=False, action='store_true',
506 help='Sync host prebuilts') 476 help='Sync host prebuilts')
507 parser.add_option('-g', '--git-sync', dest='git_sync', 477 parser.add_option('-g', '--git-sync', dest='git_sync',
508 default=False, action='store_true', 478 default=False, action='store_true',
509 help='Enable git version sync (This commits to a repo)') 479 help='Enable git version sync (This commits to a repo)')
(...skipping 16 matching lines...) Expand all
526 options, args = parser.parse_args() 496 options, args = parser.parse_args()
527 # Setup boto environment for gsutil to use 497 # Setup boto environment for gsutil to use
528 os.environ['BOTO_CONFIG'] = _BOTO_CONFIG 498 os.environ['BOTO_CONFIG'] = _BOTO_CONFIG
529 if not options.build_path: 499 if not options.build_path:
530 usage(parser, 'Error: you need provide a chroot path') 500 usage(parser, 'Error: you need provide a chroot path')
531 501
532 if not options.upload: 502 if not options.upload:
533 usage(parser, 'Error: you need to provide an upload location using -u') 503 usage(parser, 'Error: you need to provide an upload location using -u')
534 504
535 if options.filters: 505 if options.filters:
536 # TODO(davidjames): It might be nice to be able to filter private ebuilds
537 # from rsync uploads as well, some day. But for now it's not needed.
538 if not options.upload.startswith("gs://"):
539 usage(parser, 'Error: filtering only works with gs:// paths')
540 LoadPrivateFilters(options.build_path) 506 LoadPrivateFilters(options.build_path)
541 507
542 version = GetVersion() 508 version = GetVersion()
543 if options.prepend_version: 509 if options.prepend_version:
544 version = '%s-%s' % (options.prepend_version, version) 510 version = '%s-%s' % (options.prepend_version, version)
545 511
512 pkgindexes = []
513 for url in options.previous_binhost_url:
514 pkgindex = GrabRemotePackageIndex(url)
515 if pkgindex:
516 pkgindexes.append(pkgindex)
517
546 if options.sync_host: 518 if options.sync_host:
547 UploadPrebuilt(options.build_path, options.upload, version, 519 UploadPrebuilt(options.build_path, options.upload, version,
548 options.binhost_base_url, git_sync=options.git_sync, 520 options.binhost_base_url, git_sync=options.git_sync,
549 key=options.key, 521 key=options.key, pkgindexes=pkgindexes,
550 sync_binhost_conf=options.sync_binhost_conf) 522 sync_binhost_conf=options.sync_binhost_conf)
551 523
552 if options.board: 524 if options.board:
553 UploadPrebuilt(options.build_path, options.upload, version, 525 UploadPrebuilt(options.build_path, options.upload, version,
554 options.binhost_base_url, board=options.board, 526 options.binhost_base_url, board=options.board,
555 git_sync=options.git_sync, key=options.key, 527 git_sync=options.git_sync, key=options.key,
528 pkgindexes=pkgindexes,
556 sync_binhost_conf=options.sync_binhost_conf) 529 sync_binhost_conf=options.sync_binhost_conf)
557 530
558 531
559 if __name__ == '__main__': 532 if __name__ == '__main__':
560 main() 533 main()
OLDNEW
« chromite/lib/binpkg.py ('K') | « chromite/lib/binpkg.py ('k') | prebuilt_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698