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

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 feedback by dianders. Created 10 years 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):
sosa 2010/11/30 00:54:15 Why is this being removed? Part of other CL?
davidjames 2010/11/30 02:28:52 The package file filtering now happens in UploadPr
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.
310 pkgs: The packages to upload.
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 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
426 config_file.close() 370 config_file.close()
427 UpdateLocalFile(path, value, key) 371 UpdateLocalFile(path, value, key)
428 cros_build_lib.RunCommand('git add %s' % filename, cwd=cwd, shell=True) 372 cros_build_lib.RunCommand('git add %s' % filename, cwd=cwd, shell=True)
429 description = 'Update %s=%s in %s' % (key, value, filename) 373 description = 'Update %s=%s in %s' % (key, value, filename)
430 cros_build_lib.RunCommand('git commit -m "%s"' % description, cwd=cwd, 374 cros_build_lib.RunCommand('git commit -m "%s"' % description, cwd=cwd,
431 shell=True) 375 shell=True)
432 376
433 377
434 def UploadPrebuilt(build_path, upload_location, version, binhost_base_url, 378 def UploadPrebuilt(build_path, upload_location, version, binhost_base_url,
435 board=None, git_sync=False, git_sync_retries=5, 379 board=None, git_sync=False, git_sync_retries=5,
436 key='PORTAGE_BINHOST', sync_binhost_conf=False): 380 key='PORTAGE_BINHOST', pkgindexes=[],
381 sync_binhost_conf=False):
437 """Upload Host prebuilt files to Google Storage space. 382 """Upload Host prebuilt files to Google Storage space.
438 383
439 Args: 384 Args:
440 build_path: The path to the root of the chroot. 385 build_path: The path to the root of the chroot.
441 upload_location: The upload location. 386 upload_location: The upload location.
442 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
443 host packages. 388 host packages.
444 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
445 prebuilt packages generated here. 390 prebuilt packages generated here.
446 git_sync_retries: How many times to retry pushing when updating git files. 391 git_sync_retries: How many times to retry pushing when updating git files.
447 This helps avoid failures when multiple bots are modifying the same Repo. 392 This helps avoid failures when multiple bots are modifying the same Repo.
448 default: 5 393 default: 5
449 key: The variable key to update in the git file. (Default: PORTAGE_BINHOST) 394 key: The variable key to update in the git file. (Default: PORTAGE_BINHOST)
395 pkgindexes: Old uploaded prebuilts to compare against. Instead of
sosa 2010/11/30 00:54:15 Why no _?
davidjames 2010/11/30 02:28:52 Done.
396 uploading duplicate files, we just link to the old files.
450 sync_binhost_conf: If set, update binhost config file in chromiumos-overlay 397 sync_binhost_conf: If set, update binhost config file in chromiumos-overlay
451 for the current board or host. 398 for the current board or host.
452 """ 399 """
453 400
454 if not board: 401 if not board:
455 # We are uploading host packages 402 # We are uploading host packages
456 # TODO(scottz): eventually add support for different host_targets 403 # TODO(scottz): eventually add support for different host_targets
457 package_path = os.path.join(build_path, _HOST_PACKAGES_PATH) 404 package_path = os.path.join(build_path, _HOST_PACKAGES_PATH)
458 url_suffix = _REL_HOST_PATH % {'version': version, 'target': _HOST_TARGET} 405 url_suffix = _REL_HOST_PATH % {'version': version, 'target': _HOST_TARGET}
459 package_string = _HOST_TARGET 406 package_string = _HOST_TARGET
460 git_file = os.path.join(build_path, _PREBUILT_MAKE_CONF[_HOST_TARGET]) 407 git_file = os.path.join(build_path, _PREBUILT_MAKE_CONF[_HOST_TARGET])
461 binhost_conf = os.path.join(build_path, _BINHOST_CONF_DIR, 'host', 408 binhost_conf = os.path.join(build_path, _BINHOST_CONF_DIR, 'host',
462 '%s.conf' % _HOST_TARGET) 409 '%s.conf' % _HOST_TARGET)
463 else: 410 else:
464 board_path = os.path.join(build_path, _BOARD_PATH % {'board': board}) 411 board_path = os.path.join(build_path, _BOARD_PATH % {'board': board})
465 package_path = os.path.join(board_path, 'packages') 412 package_path = os.path.join(board_path, 'packages')
466 package_string = board 413 package_string = board
467 url_suffix = _REL_BOARD_PATH % {'board': board, 'version': version} 414 url_suffix = _REL_BOARD_PATH % {'board': board, 'version': version}
468 git_file = os.path.join(build_path, DetermineMakeConfFile(board)) 415 git_file = os.path.join(build_path, DetermineMakeConfFile(board))
469 binhost_conf = os.path.join(build_path, _BINHOST_CONF_DIR, 'target', 416 binhost_conf = os.path.join(build_path, _BINHOST_CONF_DIR, 'target',
470 '%s.conf' % board) 417 '%s.conf' % board)
471 remote_location = os.path.join(upload_location, url_suffix) 418 remote_location = urlparse.urljoin(upload_location, url_suffix)
419
420 # Process Packages file, removing duplicates and filtered packages.
421 pkgindex = GrabLocalPackageIndex(package_path)
422 pkgindex.SetUploadLocation(binhost_base_url, url_suffix)
423 pkgindex.RemoveFilteredPackages(lambda pkg: ShouldFilterPackage(pkg))
424 uploads = pkgindex.ResolveDuplicateUploads(pkgindexes)
425
426 # Write Packages file.
427 tmp_packages_file = pkgindex.WriteToNamedTemporaryFile()
472 428
473 if upload_location.startswith('gs://'): 429 if upload_location.startswith('gs://'):
474 upload_files = GenerateUploadDict(package_path, remote_location) 430 # Build list of files to upload.
431 upload_files = GenerateUploadDict(package_path, remote_location, uploads)
432 remote_file = urlparse.urljoin(remote_location, 'Packages')
433 upload_files[tmp_packages_file.name] = remote_file
475 434
476 print 'Uploading %s' % package_string 435 print 'Uploading %s' % package_string
477 failed_uploads = RemoteUpload(upload_files) 436 failed_uploads = RemoteUpload(upload_files)
478 if len(failed_uploads) > 1 or (None not in failed_uploads): 437 if len(failed_uploads) > 1 or (None not in failed_uploads):
479 error_msg = ['%s -> %s\n' % args for args in failed_uploads] 438 error_msg = ['%s -> %s\n' % args for args in failed_uploads]
480 raise UploadFailed('Error uploading:\n%s' % error_msg) 439 raise UploadFailed('Error uploading:\n%s' % error_msg)
481 else: 440 else:
441 pkgs = ' '.join(p['CPV'] + '.tbz2' for p in uploads)
482 ssh_server, remote_path = remote_location.split(':', 1) 442 ssh_server, remote_path = remote_location.split(':', 1)
483 cmds = ['ssh %s mkdir -p %s' % (ssh_server, remote_path), 443 d = { 'pkgindex': tmp_packages_file.name,
484 'rsync -av %s/ %s/' % (package_path, remote_location)] 444 'pkgs': pkgs,
445 'remote_path': remote_path,
446 'remote_location': remote_location,
447 'ssh_server': ssh_server }
448 cmds = ['ssh %(ssh_server)s mkdir -p %(remote_path)s' % d,
449 'rsync -av %(pkgindex)s %(remote_location)s/Packages' % d]
450 if pkgs:
451 cmds.append('rsync -Rav %(pkgs)s %(remote_location)s/' % d)
485 for cmd in cmds: 452 for cmd in cmds:
486 if not _RetryRun(cmd, shell=True): 453 if not _RetryRun(cmd, shell=True, cwd=package_path):
487 raise UploadFailed('Could not run %s' % cmd) 454 raise UploadFailed('Could not run %s' % cmd)
488 455
489 url_value = '%s/%s/' % (binhost_base_url, url_suffix) 456 url_value = '%s/%s/' % (binhost_base_url, url_suffix)
490 457
491 if git_sync: 458 if git_sync:
492 RevGitFile(git_file, url_value, retries=git_sync_retries, key=key) 459 RevGitFile(git_file, url_value, retries=git_sync_retries, key=key)
493 460
494 if sync_binhost_conf: 461 if sync_binhost_conf:
495 UpdateBinhostConfFile(binhost_conf, key, url_value) 462 UpdateBinhostConfFile(binhost_conf, key, url_value)
496 463
497 def usage(parser, msg): 464 def usage(parser, msg):
498 """Display usage message and parser help then exit with 1.""" 465 """Display usage message and parser help then exit with 1."""
499 print >> sys.stderr, msg 466 print >> sys.stderr, msg
500 parser.print_help() 467 parser.print_help()
501 sys.exit(1) 468 sys.exit(1)
502 469
503 470
504 def main(): 471 def main():
505 parser = optparse.OptionParser() 472 parser = optparse.OptionParser()
506 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url', 473 parser.add_option('-H', '--binhost-base-url', dest='binhost_base_url',
507 default=_BINHOST_BASE_URL, 474 default=_BINHOST_BASE_URL,
508 help='Base URL to use for binhost in make.conf updates') 475 help='Base URL to use for binhost in make.conf updates')
476 parser.add_option('', '--previous-binhost-url', action='append',
477 default=[], dest='previous_binhost_url',
478 help='Previous binhost URL')
509 parser.add_option('-b', '--board', dest='board', default=None, 479 parser.add_option('-b', '--board', dest='board', default=None,
510 help='Board type that was built on this machine') 480 help='Board type that was built on this machine')
511 parser.add_option('-p', '--build-path', dest='build_path', 481 parser.add_option('-p', '--build-path', dest='build_path',
512 help='Path to the chroot') 482 help='Path to the chroot')
513 parser.add_option('-s', '--sync-host', dest='sync_host', 483 parser.add_option('-s', '--sync-host', dest='sync_host',
514 default=False, action='store_true', 484 default=False, action='store_true',
515 help='Sync host prebuilts') 485 help='Sync host prebuilts')
516 parser.add_option('-g', '--git-sync', dest='git_sync', 486 parser.add_option('-g', '--git-sync', dest='git_sync',
517 default=False, action='store_true', 487 default=False, action='store_true',
518 help='Enable git version sync (This commits to a repo)') 488 help='Enable git version sync (This commits to a repo)')
(...skipping 16 matching lines...) Expand all
535 options, args = parser.parse_args() 505 options, args = parser.parse_args()
536 # Setup boto environment for gsutil to use 506 # Setup boto environment for gsutil to use
537 os.environ['BOTO_CONFIG'] = _BOTO_CONFIG 507 os.environ['BOTO_CONFIG'] = _BOTO_CONFIG
538 if not options.build_path: 508 if not options.build_path:
539 usage(parser, 'Error: you need provide a chroot path') 509 usage(parser, 'Error: you need provide a chroot path')
540 510
541 if not options.upload: 511 if not options.upload:
542 usage(parser, 'Error: you need to provide an upload location using -u') 512 usage(parser, 'Error: you need to provide an upload location using -u')
543 513
544 if options.filters: 514 if options.filters:
545 # TODO(davidjames): It might be nice to be able to filter private ebuilds
546 # from rsync uploads as well, some day. But for now it's not needed.
547 if not options.upload.startswith("gs://"):
548 usage(parser, 'Error: filtering only works with gs:// paths')
549 LoadPrivateFilters(options.build_path) 515 LoadPrivateFilters(options.build_path)
550 516
551 version = GetVersion() 517 version = GetVersion()
552 if options.prepend_version: 518 if options.prepend_version:
553 version = '%s-%s' % (options.prepend_version, version) 519 version = '%s-%s' % (options.prepend_version, version)
554 520
521 pkgindexes = []
522 for url in options.previous_binhost_url:
523 pkgindex = GrabRemotePackageIndex(url)
524 if pkgindex:
525 pkgindexes.append(pkgindex)
526
555 if options.sync_host: 527 if options.sync_host:
556 UploadPrebuilt(options.build_path, options.upload, version, 528 UploadPrebuilt(options.build_path, options.upload, version,
557 options.binhost_base_url, git_sync=options.git_sync, 529 options.binhost_base_url, git_sync=options.git_sync,
558 key=options.key, 530 key=options.key, pkgindexes=pkgindexes,
559 sync_binhost_conf=options.sync_binhost_conf) 531 sync_binhost_conf=options.sync_binhost_conf)
560 532
561 if options.board: 533 if options.board:
562 UploadPrebuilt(options.build_path, options.upload, version, 534 UploadPrebuilt(options.build_path, options.upload, version,
563 options.binhost_base_url, board=options.board, 535 options.binhost_base_url, board=options.board,
564 git_sync=options.git_sync, key=options.key, 536 git_sync=options.git_sync, key=options.key,
537 pkgindexes=pkgindexes,
565 sync_binhost_conf=options.sync_binhost_conf) 538 sync_binhost_conf=options.sync_binhost_conf)
566 539
567 540
568 if __name__ == '__main__': 541 if __name__ == '__main__':
569 main() 542 main()
OLDNEW
« bin/cbuildbot.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