1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright 2019 The Chromium OS Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7"""Updates LLVM_NEXT_HASH and uprevs the build of a package or packages. 8 9For each package, a temporary repo is created and the changes are uploaded 10for review. 11""" 12 13from __future__ import print_function 14 15import argparse 16import os 17import re 18import subprocess 19from collections import namedtuple 20 21from assert_not_in_chroot import VerifyOutsideChroot 22from failure_modes import FailureModes 23from get_llvm_hash import GetLLVMHashAndVersionFromSVNOption, is_svn_option 24import get_llvm_hash 25import llvm_patch_management 26from subprocess_helpers import ChrootRunCommand, ExecCommandAndCaptureOutput 27 28# If set to `True`, then the contents of `stdout` after executing a command will 29# be displayed to the terminal. 30verbose = False 31 32CommitContents = namedtuple('CommitContents', ['url', 'cl_number']) 33 34 35def GetCommandLineArgs(): 36 """Parses the command line for the optional command line arguments. 37 38 Returns: 39 The log level to use when retrieving the LLVM hash or google3 LLVM version, 40 the chroot path to use for executing chroot commands, 41 a list of a package or packages to update their LLVM next hash, 42 and the LLVM version to use when retrieving the LLVM hash. 43 """ 44 45 # Default path to the chroot if a path is not specified. 46 cros_root = os.path.expanduser('~') 47 cros_root = os.path.join(cros_root, 'chromiumos') 48 49 # Create parser and add optional command-line arguments. 50 parser = argparse.ArgumentParser( 51 description="Updates the build's hash for llvm-next.") 52 53 # Add argument for a specific chroot path. 54 parser.add_argument( 55 '--chroot_path', 56 default=cros_root, 57 help='the path to the chroot (default: %(default)s)') 58 59 # Add argument for specific builds to uprev and update their llvm-next hash. 60 parser.add_argument( 61 '--update_packages', 62 default=['sys-devel/llvm'], 63 required=False, 64 nargs='+', 65 help='the ebuilds to update their hash for llvm-next ' \ 66 '(default: %(default)s)') 67 68 # Add argument for whether to display command contents to `stdout`. 69 parser.add_argument( 70 '--verbose', 71 action='store_true', 72 help='display contents of a command to the terminal ' 73 '(default: %(default)s)') 74 75 # Add argument for the LLVM version to use. 76 parser.add_argument( 77 '--llvm_version', 78 type=is_svn_option, 79 required=True, 80 help='which git hash of LLVM to find. Either a svn revision, or one ' 81 'of %s' % sorted(get_llvm_hash.KNOWN_HASH_SOURCES)) 82 83 # Add argument for the mode of the patch management when handling patches. 84 parser.add_argument( 85 '--failure_mode', 86 default=FailureModes.FAIL.value, 87 choices=[FailureModes.FAIL.value, FailureModes.CONTINUE.value, 88 FailureModes.DISABLE_PATCHES.value, 89 FailureModes.REMOVE_PATCHES.value], 90 help='the mode of the patch manager when handling failed patches ' \ 91 '(default: %(default)s)') 92 93 # Add argument for the patch metadata file. 94 parser.add_argument( 95 '--patch_metadata_file', 96 default='PATCHES.json', 97 help='the .json file that has all the patches and their ' 98 'metadata if applicable (default: PATCHES.json inside $FILESDIR)') 99 100 # Parse the command line. 101 args_output = parser.parse_args() 102 103 # FIXME: We shouldn't be using globals here, but until we fix it, make pylint 104 # stop complaining about it. 105 # pylint: disable=global-statement 106 global verbose 107 108 verbose = args_output.verbose 109 110 return args_output 111 112 113def GetChrootBuildPaths(chromeos_root, package_list): 114 """Gets the chroot path(s) of the package(s). 115 116 Args: 117 chromeos_root: The absolute path to the chroot to 118 use for executing chroot commands. 119 package_list: A list of a package/packages to 120 be used to find their chroot path. 121 122 Returns: 123 A list of a chroot path/chroot paths of the package's ebuild file. 124 125 Raises: 126 ValueError: Failed to get the chroot path of a package. 127 """ 128 129 chroot_paths = [] 130 131 # Find the chroot path for each package's ebuild. 132 for cur_package in sorted(set(package_list)): 133 # Cmd to find the chroot path for the package. 134 equery_cmd = ['equery', 'w', cur_package] 135 136 chroot_path = ChrootRunCommand(chromeos_root, equery_cmd, verbose=verbose) 137 138 chroot_paths.append(chroot_path.strip()) 139 140 return chroot_paths 141 142 143def _ConvertChrootPathsToSymLinkPaths(chromeos_root, chroot_file_paths): 144 """Converts the chroot path(s) to absolute symlink path(s). 145 146 Args: 147 chromeos_root: The absolute path to the chroot. 148 chroot_file_paths: A list of a chroot path/chroot paths to convert to 149 a absolute symlink path/symlink paths. 150 151 Returns: 152 A list of absolute path(s) which are symlinks that point to 153 the ebuild of the package(s). 154 155 Raises: 156 ValueError: Invalid prefix for the chroot path or 157 invalid chroot path(s) were provided. 158 """ 159 160 symlink_file_paths = [] 161 162 chroot_prefix = '/mnt/host/source/' 163 164 # Iterate through the chroot paths. 165 # 166 # For each chroot file path, remove '/mnt/host/source/' prefix 167 # and combine the chroot path with the result and add it to the list. 168 for cur_chroot_file_path in chroot_file_paths: 169 if not cur_chroot_file_path.startswith(chroot_prefix): 170 raise ValueError( 171 'Invalid prefix for the chroot path: %s' % cur_chroot_file_path) 172 173 rel_path = cur_chroot_file_path[len(chroot_prefix):] 174 175 # combine the chromeos root path + '/src/...' 176 absolute_symlink_path = os.path.join(chromeos_root, rel_path) 177 178 symlink_file_paths.append(absolute_symlink_path) 179 180 return symlink_file_paths 181 182 183def GetEbuildPathsFromSymLinkPaths(symlinks): 184 """Reads the symlink(s) to get the ebuild path(s) to the package(s). 185 186 Args: 187 symlinks: A list of absolute path symlink/symlinks that point 188 to the package's ebuild. 189 190 Returns: 191 A dictionary where the key is the absolute path of the symlink and the value 192 is the absolute path to the ebuild that was read from the symlink. 193 194 Raises: 195 ValueError: Invalid symlink(s) were provided. 196 """ 197 198 # A dictionary that holds: 199 # key: absolute symlink path 200 # value: absolute ebuild path 201 resolved_paths = {} 202 203 # Iterate through each symlink. 204 # 205 # For each symlink, check that it is a valid symlink, 206 # and then construct the ebuild path, and 207 # then add the ebuild path to the dict. 208 for cur_symlink in symlinks: 209 if not os.path.islink(cur_symlink): 210 raise ValueError('Invalid symlink provided: %s' % cur_symlink) 211 212 # Construct the absolute path to the ebuild. 213 ebuild_path = os.path.realpath(cur_symlink) 214 215 if cur_symlink not in resolved_paths: 216 resolved_paths[cur_symlink] = ebuild_path 217 218 return resolved_paths 219 220 221def UpdateBuildLLVMNextHash(ebuild_path, llvm_hash, llvm_version): 222 """Updates the build's LLVM_NEXT_HASH. 223 224 The build changes are staged for commit in the temporary repo. 225 226 Args: 227 ebuild_path: The absolute path to the ebuild. 228 llvm_hash: The new LLVM hash to use for LLVM_NEXT_HASH. 229 llvm_version: The revision number of 'llvm_hash'. 230 231 Raises: 232 ValueError: Invalid ebuild path provided or failed to stage the commit 233 of the changes or failed to update the LLVM hash. 234 """ 235 236 # Iterate through each ebuild. 237 # 238 # For each ebuild, read the file in 239 # advance and then create a temporary file 240 # that gets updated with the new LLVM hash 241 # and revision number and then the ebuild file 242 # gets updated to the temporary file. 243 244 if not os.path.isfile(ebuild_path): 245 raise ValueError('Invalid ebuild path provided: %s' % ebuild_path) 246 247 # Create regex that finds 'LLVM_NEXT_HASH'. 248 llvm_regex = re.compile('^LLVM_NEXT_HASH=\"[a-z0-9]+\"') 249 250 temp_ebuild_file = '%s.temp' % ebuild_path 251 252 # A flag for whether 'LLVM_NEXT_HASH=...' was updated. 253 is_updated = False 254 255 with open(ebuild_path) as ebuild_file: 256 # write updates to a temporary file in case of interrupts 257 with open(temp_ebuild_file, 'w') as temp_file: 258 for cur_line in ReplaceLLVMNextHash(ebuild_file, is_updated, llvm_regex, 259 llvm_hash, llvm_version): 260 temp_file.write(cur_line) 261 262 os.rename(temp_ebuild_file, ebuild_path) 263 264 # Get the path to the parent directory. 265 parent_dir = os.path.dirname(ebuild_path) 266 267 # Stage the changes. 268 stage_changes_cmd = ['git', '-C', parent_dir, 'add', ebuild_path] 269 270 ExecCommandAndCaptureOutput(stage_changes_cmd, verbose=verbose) 271 272 273def ReplaceLLVMNextHash(ebuild_lines, is_updated, llvm_regex, llvm_hash, 274 llvm_version): 275 """Iterates through the ebuild file and updates the 'LLVM_NEXT_HASH'. 276 277 Args: 278 ebuild_lines: The contents of the ebuild file. 279 is_updated: A flag for whether 'LLVM_NEXT_HASH' was updated. 280 llvm_regex: The regex object for finding 'LLVM_NEXT_HASH=...' when 281 iterating through the contents of the file. 282 llvm_hash: The new LLVM hash to use for LLVM_NEXT_HASH. 283 llvm_version: The revision number of 'llvm_hash'. 284 """ 285 286 for cur_line in ebuild_lines: 287 if not is_updated and llvm_regex.search(cur_line): 288 # Update the LLVM next hash and revision number. 289 cur_line = 'LLVM_NEXT_HASH=\"%s\" # r%d\n' % (llvm_hash, llvm_version) 290 291 is_updated = True 292 293 yield cur_line 294 295 if not is_updated: # failed to update 'LLVM_NEXT_HASH' 296 raise ValueError('Failed to update the LLVM hash.') 297 298 299def UprevEbuild(symlink): 300 """Uprevs the ebuild's revision number. 301 302 Increases the revision number by 1 and stages the change in 303 the temporary repo. 304 305 Args: 306 symlink: The absolute path of the symlink that points to 307 the ebuild of the package. 308 309 Raises: 310 ValueError: Failed to uprev the symlink or failed to stage the changes. 311 """ 312 313 if not os.path.islink(symlink): 314 raise ValueError('Invalid symlink provided: %s' % symlink) 315 316 # Find the revision number and increment it by 1. 317 new_symlink, is_changed = re.subn( 318 r'r([0-9]+).ebuild', 319 lambda match: 'r%s.ebuild' % str(int(match.group(1)) + 1), 320 symlink, 321 count=1) 322 323 if not is_changed: # failed to increment the revision number 324 raise ValueError('Failed to uprev the ebuild.') 325 326 path_to_symlink_dir = os.path.dirname(symlink) 327 328 # Stage the new symlink for commit. 329 stage_symlink_cmd = [ 330 'git', '-C', path_to_symlink_dir, 'mv', symlink, new_symlink 331 ] 332 333 ExecCommandAndCaptureOutput(stage_symlink_cmd, verbose=verbose) 334 335 336def _CreateRepo(path_to_repo_dir, llvm_hash): 337 """Creates a temporary repo for the changes. 338 339 Args: 340 path_to_repo_dir: The absolute path to the repo. 341 llvm_hash: The LLVM hash to use for the name of the repo. 342 343 Raises: 344 ValueError: Failed to create a repo in that directory. 345 """ 346 347 if not os.path.isdir(path_to_repo_dir): 348 raise ValueError('Invalid directory path provided: %s' % path_to_repo_dir) 349 350 reset_changes_cmd = [ 351 'git', 352 '-C', 353 path_to_repo_dir, 354 'reset', 355 'HEAD', 356 '--hard', 357 ] 358 359 ExecCommandAndCaptureOutput(reset_changes_cmd, verbose=verbose) 360 361 create_repo_cmd = ['repo', 'start', 'llvm-next-update-%s' % llvm_hash] 362 363 ExecCommandAndCaptureOutput( 364 create_repo_cmd, cwd=path_to_repo_dir, verbose=verbose) 365 366 367def _DeleteRepo(path_to_repo_dir, llvm_hash): 368 """Deletes the temporary repo. 369 370 Args: 371 path_to_repo_dir: The absolute path of the repo. 372 llvm_hash: The LLVM hash used for the name of the repo. 373 374 Raises: 375 ValueError: Failed to delete the repo in that directory. 376 """ 377 378 if not os.path.isdir(path_to_repo_dir): 379 raise ValueError('Invalid directory path provided: %s' % path_to_repo_dir) 380 381 checkout_to_master_cmd = [ 382 'git', '-C', path_to_repo_dir, 'checkout', 'cros/master' 383 ] 384 385 ExecCommandAndCaptureOutput(checkout_to_master_cmd, verbose=verbose) 386 387 reset_head_cmd = ['git', '-C', path_to_repo_dir, 'reset', 'HEAD', '--hard'] 388 389 ExecCommandAndCaptureOutput(reset_head_cmd, verbose=verbose) 390 391 delete_repo_cmd = [ 392 'git', '-C', path_to_repo_dir, 'branch', '-D', 393 'llvm-next-update-%s' % llvm_hash 394 ] 395 396 ExecCommandAndCaptureOutput(delete_repo_cmd, verbose=verbose) 397 398 399def GetGerritRepoUploadContents(repo_upload_contents): 400 """Parses 'repo upload' to get the Gerrit commit URL and CL number. 401 402 Args: 403 repo_upload_contents: The contents of the 'repo upload' command. 404 405 Returns: 406 A nametuple that has two (key, value) pairs, where the first pair is the 407 Gerrit commit URL and the second pair is the change list number. 408 409 Raises: 410 ValueError: The contents of the 'repo upload' command did not contain a 411 Gerrit commit URL. 412 """ 413 414 found_url = re.search( 415 r'https://chromium-review.googlesource.com/c/' 416 r'chromiumos/overlays/chromiumos-overlay/\+/([0-9]+)', 417 repo_upload_contents) 418 419 if not found_url: 420 raise ValueError('Failed to find change list URL.') 421 422 cl_number = int(found_url.group(1)) 423 424 return CommitContents(url=found_url.group(0), cl_number=cl_number) 425 426 427def UploadChanges(path_to_repo_dir, llvm_hash, commit_messages): 428 """Uploads the changes (updating LLVM next hash and uprev symlink) for review. 429 430 Args: 431 path_to_repo_dir: The absolute path to the repo where changes were made. 432 llvm_hash: The LLVM hash used for the name of the repo. 433 commit_messages: A string of commit message(s) (i.e. '-m [message]' 434 of the changes made. 435 436 Returns: 437 A nametuple that has two (key, value) pairs, where the first pair is the 438 Gerrit commit URL and the second pair is the change list number. 439 440 Raises: 441 ValueError: Failed to create a commit or failed to upload the 442 changes for review. 443 """ 444 445 if not os.path.isdir(path_to_repo_dir): 446 raise ValueError('Invalid directory path provided: %s' % path_to_repo_dir) 447 448 commit_cmd = [ 449 'git', 450 'commit', 451 ] 452 commit_cmd.extend(commit_messages) 453 454 ExecCommandAndCaptureOutput(commit_cmd, cwd=path_to_repo_dir, verbose=verbose) 455 456 # Upload the changes for review. 457 upload_change_cmd = ( 458 'yes | repo upload --br=llvm-next-update-%s --no-verify' % llvm_hash) 459 460 # Pylint currently doesn't lint things in py3 mode, and py2 didn't allow 461 # users to specify `encoding`s for Popen. Hence, pylint is "wrong" here. 462 # pylint: disable=unexpected-keyword-arg 463 464 # NOTE: Need `shell=True` in order to pipe `yes` into `repo upload ...`. 465 # 466 # The CL URL is sent to 'stderr', so need to redirect 'stderr' to 'stdout'. 467 upload_changes_obj = subprocess.Popen( 468 upload_change_cmd, 469 cwd=path_to_repo_dir, 470 shell=True, 471 encoding='UTF-8', 472 stdout=subprocess.PIPE, 473 stderr=subprocess.STDOUT) 474 475 out, _ = upload_changes_obj.communicate() 476 477 if upload_changes_obj.returncode: # Failed to upload changes. 478 print(out) 479 raise ValueError('Failed to upload changes for review') 480 481 return GetGerritRepoUploadContents(out.rstrip()) 482 483 484def CreatePathDictionaryFromPackages(chroot_path, update_packages): 485 """Creates a symlink and ebuild path pair dictionary from the packages. 486 487 Args: 488 chroot_path: The absolute path to the chroot. 489 update_packages: The filtered packages to be updated. 490 491 Returns: 492 A dictionary where the key is the absolute path to the symlink 493 of the package and the value is the absolute path to the ebuild of 494 the package. 495 """ 496 497 # Construct a list containing the chroot file paths of the package(s). 498 chroot_file_paths = GetChrootBuildPaths(chroot_path, update_packages) 499 500 # Construct a list containing the symlink(s) of the package(s). 501 symlink_file_paths = _ConvertChrootPathsToSymLinkPaths( 502 chroot_path, chroot_file_paths) 503 504 # Create a dictionary where the key is the absolute path of the symlink to 505 # the package and the value is the absolute path to the ebuild of the package. 506 return GetEbuildPathsFromSymLinkPaths(symlink_file_paths) 507 508 509def RemovePatchesFromFilesDir(patches_to_remove): 510 """Removes the patches from $FILESDIR of a package. 511 512 Args: 513 patches_to_remove: A list where each entry is the absolute path to a patch. 514 515 Raises: 516 ValueError: Failed to remove a patch in $FILESDIR. 517 """ 518 519 for cur_patch in patches_to_remove: 520 remove_patch_cmd = [ 521 'git', '-C', 522 os.path.dirname(cur_patch), 'rm', '-f', cur_patch 523 ] 524 525 ExecCommandAndCaptureOutput(remove_patch_cmd, verbose=verbose) 526 527 528def StagePatchMetadataFileForCommit(patch_metadata_file_path): 529 """Stages the updated patch metadata file for commit. 530 531 Args: 532 patch_metadata_file_path: The absolute path to the patch metadata file. 533 534 Raises: 535 ValueError: Failed to stage the patch metadata file for commit or invalid 536 patch metadata file. 537 """ 538 539 if not os.path.isfile(patch_metadata_file_path): 540 raise ValueError( 541 'Invalid patch metadata file provided: %s' % patch_metadata_file_path) 542 543 # Cmd to stage the patch metadata file for commit. 544 stage_patch_file = [ 545 'git', '-C', 546 os.path.dirname(patch_metadata_file_path), 'add', patch_metadata_file_path 547 ] 548 549 ExecCommandAndCaptureOutput(stage_patch_file, verbose=verbose) 550 551 552def StagePackagesPatchResultsForCommit(package_info_dict, commit_messages): 553 """Stages the patch results of the packages to the commit message. 554 555 Args: 556 package_info_dict: A dictionary where the key is the package name and the 557 value is a dictionary that contains information about the patches of the 558 package (key). 559 commit_messages: The commit message that has the updated ebuilds and 560 upreving information. 561 """ 562 563 # For each package, check if any patches for that package have 564 # changed, if so, add which patches have changed to the commit 565 # message. 566 for package_name, patch_info_dict in package_info_dict.items(): 567 if patch_info_dict['disabled_patches'] or \ 568 patch_info_dict['removed_patches'] or \ 569 patch_info_dict['modified_metadata']: 570 cur_package_header = 'For the package %s:' % package_name 571 commit_messages.append('-m %s' % cur_package_header) 572 573 # Add to the commit message that the patch metadata file was modified. 574 if patch_info_dict['modified_metadata']: 575 patch_metadata_path = patch_info_dict['modified_metadata'] 576 commit_messages.append('-m %s' % 'The patch metadata file %s was ' 577 'modified' % os.path.basename(patch_metadata_path)) 578 579 StagePatchMetadataFileForCommit(patch_metadata_path) 580 581 # Add each disabled patch to the commit message. 582 if patch_info_dict['disabled_patches']: 583 commit_messages.append('-m %s' % 'The following patches were disabled:') 584 585 for patch_path in patch_info_dict['disabled_patches']: 586 commit_messages.append('-m %s' % os.path.basename(patch_path)) 587 588 # Add each removed patch to the commit message. 589 if patch_info_dict['removed_patches']: 590 commit_messages.append('-m %s' % 'The following patches were removed:') 591 592 for patch_path in patch_info_dict['removed_patches']: 593 commit_messages.append('-m %s' % os.path.basename(patch_path)) 594 595 RemovePatchesFromFilesDir(patch_info_dict['removed_patches']) 596 597 return commit_messages 598 599 600def UpdatePackages(packages, llvm_hash, llvm_version, chroot_path, 601 patch_metadata_file, mode, svn_option): 602 """Updates the package's LLVM_NEXT_HASH and uprevs the ebuild. 603 604 A temporary repo is created for the changes. The changes are 605 then uploaded for review. 606 607 Args: 608 packages: A list of all the packages that are going to be updated. 609 llvm_hash: The LLVM hash to use for 'LLVM_NEXT_HASH'. 610 llvm_version: The LLVM version of the 'llvm_hash'. 611 chroot_path: The absolute path to the chroot. 612 patch_metadata_file: The name of the .json file in '$FILESDIR/' that has 613 the patches and its metadata. 614 mode: The mode of the patch manager when handling an applicable patch 615 that failed to apply. 616 Ex: 'FailureModes.FAIL' 617 svn_option: The git hash to use based off of the svn option. 618 Ex: 'google3', 'tot', or <svn_version> such as 365123 619 620 Returns: 621 A nametuple that has two (key, value) pairs, where the first pair is the 622 Gerrit commit URL and the second pair is the change list number. 623 """ 624 625 # Determines whether to print the result of each executed command. 626 llvm_patch_management.verbose = verbose 627 628 # Construct a dictionary where the key is the absolute path of the symlink to 629 # the package and the value is the absolute path to the ebuild of the package. 630 paths_dict = CreatePathDictionaryFromPackages(chroot_path, packages) 631 632 repo_path = os.path.dirname(next(iter(paths_dict.values()))) 633 634 _CreateRepo(repo_path, llvm_hash) 635 636 try: 637 if svn_option in get_llvm_hash.KNOWN_HASH_SOURCES: 638 commit_message_header = ('llvm-next/%s: upgrade to %s (r%d)' % 639 (svn_option, llvm_hash, llvm_version)) 640 else: 641 commit_message_header = ( 642 'llvm-next: upgrade to %s (r%d)' % (llvm_hash, llvm_version)) 643 644 commit_messages = ['-m %s' % commit_message_header] 645 646 commit_messages.append('-m The following packages have been updated:') 647 648 # Holds the list of packages that are updating. 649 packages = [] 650 651 # Iterate through the dictionary. 652 # 653 # For each iteration: 654 # 1) Update the ebuild's LLVM_NEXT_HASH. 655 # 2) Uprev the ebuild (symlink). 656 # 3) Add the modified package to the commit message. 657 for symlink_path, ebuild_path in paths_dict.items(): 658 path_to_ebuild_dir = os.path.dirname(ebuild_path) 659 660 UpdateBuildLLVMNextHash(ebuild_path, llvm_hash, llvm_version) 661 662 UprevEbuild(symlink_path) 663 664 cur_dir_name = os.path.basename(path_to_ebuild_dir) 665 parent_dir_name = os.path.basename(os.path.dirname(path_to_ebuild_dir)) 666 667 packages.append('%s/%s' % (parent_dir_name, cur_dir_name)) 668 669 new_commit_message = '%s/%s' % (parent_dir_name, cur_dir_name) 670 671 commit_messages.append('-m %s' % new_commit_message) 672 673 # Handle the patches for each package. 674 package_info_dict = llvm_patch_management.UpdatePackagesPatchMetadataFile( 675 chroot_path, llvm_version, patch_metadata_file, packages, mode) 676 677 # Update the commit message if changes were made to a package's patches. 678 commit_messages = StagePackagesPatchResultsForCommit( 679 package_info_dict, commit_messages) 680 681 change_list = UploadChanges(repo_path, llvm_hash, commit_messages) 682 683 finally: 684 _DeleteRepo(repo_path, llvm_hash) 685 686 return change_list 687 688 689def main(): 690 """Updates the LLVM next hash for each package. 691 692 Raises: 693 AssertionError: The script was run inside the chroot. 694 """ 695 696 VerifyOutsideChroot() 697 698 args_output = GetCommandLineArgs() 699 700 svn_option = args_output.llvm_version 701 702 llvm_hash, llvm_version = GetLLVMHashAndVersionFromSVNOption(svn_option) 703 704 change_list = UpdatePackages( 705 args_output.update_packages, llvm_hash, llvm_version, 706 args_output.chroot_path, args_output.patch_metadata_file, 707 FailureModes(args_output.failure_mode), svn_option) 708 709 print('Successfully updated packages to %d' % llvm_version) 710 print('Gerrit URL: %s' % change_list.url) 711 print('Change list number: %d' % change_list.cl_number) 712 713 714if __name__ == '__main__': 715 main() 716