1#!/usr/bin/env python3 2# Copyright 2019 The ChromiumOS Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Updates the LLVM hash and uprevs the build of the specified packages. 7 8For each package, a temporary repo is created and the changes are uploaded 9for review. 10""" 11 12import argparse 13import dataclasses 14import enum 15import os 16from pathlib import Path 17import re 18import subprocess 19import textwrap 20from typing import Dict, Iterable, Iterator, List, Optional, Union 21 22import atomic_write_file 23import chroot 24import failure_modes 25import get_llvm_hash 26import git 27import manifest_utils 28import patch_utils 29import subprocess_helpers 30 31 32# Default list of packages to update. 33DEFAULT_PACKAGES = patch_utils.CHROMEOS_PATCHES_JSON_PACKAGES 34 35DEFAULT_MANIFEST_PACKAGES = ["sys-devel/llvm"] 36 37 38# Specify which LLVM hash to update 39class LLVMVariant(enum.Enum): 40 """Represent the LLVM hash in an ebuild file to update.""" 41 42 current = "LLVM_HASH" 43 next = "LLVM_NEXT_HASH" 44 45 46@dataclasses.dataclass(frozen=True, eq=True) 47class ChrootOpts: 48 """A class that holds chroot options.""" 49 50 chromeos_root: Path 51 chroot_name: str = "chroot" 52 out_name: str = "out" 53 54 55class PortagePackage: 56 """Represents a portage package with location info.""" 57 58 def __init__(self, chroot_opts: ChrootOpts, package: str): 59 """Create a new PortagePackage. 60 61 Args: 62 chroot_opts: options that specify the ChromeOS chroot to use. 63 package: "category/package" string. 64 """ 65 self.package = package 66 potential_ebuild_path = PortagePackage.find_package_ebuild( 67 chroot_opts, package 68 ) 69 if potential_ebuild_path.is_symlink(): 70 self.uprev_target: Optional[Path] = potential_ebuild_path.absolute() 71 self.ebuild_path = potential_ebuild_path.resolve() 72 else: 73 # Should have a 9999 ebuild, no uprevs needed. 74 self.uprev_target = None 75 self.ebuild_path = potential_ebuild_path.absolute() 76 77 @staticmethod 78 def find_package_ebuild(chroot_opts: ChrootOpts, package: str) -> Path: 79 """Look up the package's ebuild location.""" 80 chromeos_root_str = str(chroot_opts.chromeos_root) 81 ebuild_paths = chroot.GetChrootEbuildPaths( 82 chromeos_root_str, 83 [package], 84 chroot_opts.chroot_name, 85 chroot_opts.out_name, 86 ) 87 converted = chroot.ConvertChrootPathsToAbsolutePaths( 88 chromeos_root_str, ebuild_paths 89 )[0] 90 return Path(converted) 91 92 def package_dir(self) -> Path: 93 """Return the package directory.""" 94 return self.ebuild_path.parent 95 96 def update( 97 self, llvm_variant: LLVMVariant, git_hash: str, svn_version: int 98 ): 99 """Update the package with the new LLVM git sha and revision. 100 101 Args: 102 llvm_variant: Which LLVM hash to update. 103 Either LLVM_HASH or LLVM_NEXT_HASH. 104 git_hash: Upstream LLVM git hash to update to. 105 svn_version: Matching LLVM revision string for the git_hash. 106 """ 107 live_ebuild = self.live_ebuild() 108 if live_ebuild: 109 # Working with a -9999 ebuild package here, no 110 # upreving. 111 UpdateEbuildLLVMHash( 112 live_ebuild, llvm_variant, git_hash, svn_version 113 ) 114 return 115 if not self.uprev_target: 116 # We can exit early if we're not working with a live ebuild, 117 # and we don't have something to uprev. 118 raise RuntimeError( 119 "Cannot update: no live ebuild or symlink found" 120 f" for {self.package}" 121 ) 122 123 UpdateEbuildLLVMHash( 124 self.ebuild_path, llvm_variant, git_hash, svn_version 125 ) 126 if llvm_variant == LLVMVariant.current: 127 UprevEbuildToVersion(str(self.uprev_target), svn_version, git_hash) 128 else: 129 UprevEbuildSymlink(str(self.uprev_target)) 130 131 def live_ebuild(self) -> Optional[Path]: 132 """Path to the live ebuild if it exists. 133 134 Returns: 135 The patch to the live ebuild if it exists. None otherwise. 136 """ 137 matches = self.package_dir().glob("*-9999.ebuild") 138 return next(matches, None) 139 140 141def defaultCrosRoot() -> Path: 142 """Get default location of chromeos_path. 143 144 The logic assumes that the cros_root is ~/chromiumos, unless llvm_tools is 145 inside of a CrOS checkout, in which case that checkout should be used. 146 147 Returns: 148 The best guess location for the cros checkout. 149 """ 150 llvm_tools_path = os.path.realpath(os.path.dirname(__file__)) 151 if llvm_tools_path.endswith("src/third_party/toolchain-utils/llvm_tools"): 152 return Path(llvm_tools_path).parent.parent.parent.parent 153 return Path.home() / "chromiumos" 154 155 156def GetCommandLineArgs(): 157 """Parses the command line for the optional command line arguments. 158 159 Returns: 160 The log level to use when retrieving the LLVM hash or google3 LLVM 161 version, the chroot path to use for executing chroot commands, a list 162 of a package or packages to update their LLVM next hash, and the LLVM 163 version to use when retrieving the LLVM hash. 164 """ 165 166 # Create parser and add optional command-line arguments. 167 parser = argparse.ArgumentParser( 168 description="Updates the build's hash for llvm-next." 169 ) 170 171 # Add argument for a specific chroot path. 172 parser.add_argument( 173 "--chromeos_path", 174 type=Path, 175 default=defaultCrosRoot(), 176 help="the path to the chroot (default: %(default)s)", 177 ) 178 179 # Add argument for specific builds to uprev and update their llvm-next 180 # hash. 181 parser.add_argument( 182 "--update_packages", 183 default=",".join(DEFAULT_PACKAGES), 184 help="Comma-separated ebuilds to update llvm-next hash for " 185 "(default: %(default)s)", 186 ) 187 188 parser.add_argument( 189 "--manifest_packages", 190 default="", 191 help="Comma-separated ebuilds to update manifests for " 192 "(default: %(default)s)", 193 ) 194 195 # Add argument for the LLVM hash to update 196 parser.add_argument( 197 "--is_llvm_next", 198 action="store_true", 199 help="which llvm hash to update. If specified, update LLVM_NEXT_HASH. " 200 "Otherwise, update LLVM_HASH", 201 ) 202 203 # Add argument for the LLVM version to use. 204 parser.add_argument( 205 "--llvm_version", 206 type=get_llvm_hash.IsSvnOption, 207 required=True, 208 help="which git hash to use. Either a svn revision, or one " 209 f"of {sorted(get_llvm_hash.KNOWN_HASH_SOURCES)}", 210 ) 211 212 # Add argument for the mode of the patch management when handling patches. 213 parser.add_argument( 214 "--failure_mode", 215 default=failure_modes.FailureModes.FAIL.value, 216 choices=[ 217 failure_modes.FailureModes.FAIL.value, 218 failure_modes.FailureModes.CONTINUE.value, 219 failure_modes.FailureModes.DISABLE_PATCHES.value, 220 ], 221 help="the mode of the patch manager when handling failed patches " 222 "(default: %(default)s)", 223 ) 224 225 # Add argument for the patch metadata file. 226 parser.add_argument( 227 "--patch_metadata_file", 228 default="PATCHES.json", 229 help="the .json file that has all the patches and their " 230 "metadata if applicable (default: PATCHES.json inside $FILESDIR)", 231 ) 232 parser.add_argument( 233 "--no_repo_manifest", 234 dest="repo_manifest", 235 action="store_false", 236 help="Skip updating the llvm-project revision attribute" 237 " in the internal manifest.", 238 ) 239 parser.add_argument( 240 "--no_delete_branch", 241 action="store_true", 242 help="Do not delete the created overlay branch.", 243 ) 244 parser.add_argument( 245 "--no_upload_changes", 246 action="store_true", 247 help="Do not upload changes to gerrit.", 248 ) 249 parser.add_argument( 250 "--no_patching", 251 action="store_true", 252 help="Do not check or update PATCHES.json.", 253 ) 254 # Parse the command line. 255 return parser.parse_args() 256 257 258def UpdateEbuildLLVMHash( 259 ebuild_path: Path, 260 llvm_variant: LLVMVariant, 261 git_hash: str, 262 svn_version: int, 263) -> None: 264 """Updates the LLVM hash in the ebuild. 265 266 The build changes are staged for commit in the temporary repo. 267 268 Args: 269 ebuild_path: The absolute path to the ebuild. 270 llvm_variant: Which LLVM hash to update. 271 git_hash: The new git hash. 272 svn_version: The SVN-style revision number of git_hash. 273 274 Raises: 275 ValueError: Invalid ebuild path provided or failed to stage the commit 276 of the changes or failed to update the LLVM hash. 277 """ 278 279 # For each ebuild, read the file in 280 # advance and then create a temporary file 281 # that gets updated with the new LLVM hash 282 # and revision number and then the ebuild file 283 # gets updated to the temporary file. 284 if not os.path.isfile(ebuild_path): 285 raise ValueError(f"Invalid ebuild path provided: {ebuild_path}") 286 287 with open(ebuild_path, encoding="utf-8") as ebuild_file: 288 new_lines = list( 289 ReplaceLLVMHash(ebuild_file, llvm_variant, git_hash, svn_version) 290 ) 291 with atomic_write_file.atomic_write( 292 ebuild_path, "w", encoding="utf-8" 293 ) as ebuild_file: 294 ebuild_file.writelines(new_lines) 295 # Stage the changes. 296 subprocess.check_output( 297 ["git", "-C", ebuild_path.parent, "add", ebuild_path] 298 ) 299 300 301def ReplaceLLVMHash( 302 ebuild_lines: Iterable[str], 303 llvm_variant: LLVMVariant, 304 git_hash: str, 305 svn_version: int, 306) -> Iterator[str]: 307 """Updates the LLVM git hash. 308 309 Args: 310 ebuild_lines: The contents of the ebuild file. 311 llvm_variant: The LLVM hash to update. 312 git_hash: The new git hash. 313 svn_version: The SVN-style revision number of git_hash. 314 315 Yields: 316 lines of the modified ebuild file 317 """ 318 is_updated = False 319 llvm_regex = re.compile( 320 "^" + re.escape(llvm_variant.value) + '="[a-z0-9]+"' 321 ) 322 for cur_line in ebuild_lines: 323 if not is_updated and llvm_regex.search(cur_line): 324 # Update the git hash and revision number. 325 cur_line = f'{llvm_variant.value}="{git_hash}" # r{svn_version}\n' 326 327 is_updated = True 328 329 yield cur_line 330 331 if not is_updated: 332 raise ValueError(f"Failed to update {llvm_variant.value}") 333 334 335def UprevEbuildSymlink(symlink: str) -> None: 336 """Uprevs the symlink's revision number. 337 338 Increases the revision number by 1 and stages the change in 339 the temporary repo. 340 341 Args: 342 symlink: The absolute path of an ebuild symlink. 343 344 Raises: 345 ValueError: Failed to uprev the symlink or failed to stage the changes. 346 """ 347 348 if not os.path.islink(symlink): 349 raise ValueError(f"Invalid symlink provided: {symlink}") 350 351 new_symlink, is_changed = re.subn( 352 r"r([0-9]+).ebuild", 353 lambda match: "r%s.ebuild" % str(int(match.group(1)) + 1), 354 symlink, 355 count=1, 356 ) 357 358 if not is_changed: 359 raise ValueError("Failed to uprev the symlink.") 360 361 # rename the symlink 362 subprocess.check_output( 363 ["git", "-C", os.path.dirname(symlink), "mv", symlink, new_symlink] 364 ) 365 366 367def UprevEbuildToVersion(symlink: str, svn_version: int, git_hash: str) -> None: 368 """Uprevs the ebuild's revision number. 369 370 Increases the revision number by 1 and stages the change in 371 the temporary repo. 372 373 Args: 374 symlink: The absolute path of an ebuild symlink. 375 svn_version: The SVN-style revision number of git_hash. 376 git_hash: The new git hash. 377 378 Raises: 379 ValueError: Failed to uprev the ebuild or failed to stage the changes. 380 AssertionError: No llvm version provided for an LLVM uprev 381 """ 382 383 if not os.path.islink(symlink): 384 raise ValueError(f"Invalid symlink provided: {symlink}") 385 386 ebuild = os.path.realpath(symlink) 387 llvm_major_version = get_llvm_hash.GetLLVMMajorVersion(git_hash) 388 # llvm 389 package = os.path.basename(os.path.dirname(symlink)) 390 if not package: 391 raise ValueError("Tried to uprev an unknown package") 392 if package == "llvm": 393 new_ebuild, is_changed = re.subn( 394 r"(\d+)\.(\d+)_pre([0-9]+)(_p[0-9]+)?", 395 "%s.\\2_pre%s" 396 % ( 397 llvm_major_version, 398 str(svn_version), 399 ), 400 ebuild, 401 count=1, 402 ) 403 # any other package 404 else: 405 new_ebuild, is_changed = re.subn( 406 r"(\d+)\.(\d+)_pre([0-9]+)", 407 "%s.\\2_pre%s" % (llvm_major_version, str(svn_version)), 408 ebuild, 409 count=1, 410 ) 411 412 if not is_changed: # failed to increment the revision number 413 raise ValueError("Failed to uprev the ebuild.") 414 415 symlink_dir = os.path.dirname(symlink) 416 417 # Rename the ebuild 418 subprocess.check_output( 419 ["git", "-C", symlink_dir, "mv", ebuild, new_ebuild] 420 ) 421 422 # Create a symlink of the renamed ebuild 423 new_symlink = new_ebuild[: -len(".ebuild")] + "-r1.ebuild" 424 subprocess.check_output(["ln", "-s", "-r", new_ebuild, new_symlink]) 425 subprocess.check_output(["git", "-C", symlink_dir, "add", new_symlink]) 426 # Remove the old symlink 427 subprocess.check_output(["git", "-C", symlink_dir, "rm", symlink]) 428 429 430def RemovePatchesFromFilesDir(patches: Iterable[str]) -> None: 431 """Removes the patches from $FILESDIR of a package. 432 433 Args: 434 patches: A list of absolute paths of patches to remove 435 436 Raises: 437 ValueError: Failed to remove a patch in $FILESDIR. 438 """ 439 440 for patch in patches: 441 subprocess.check_output( 442 ["git", "-C", os.path.dirname(patch), "rm", "-f", patch] 443 ) 444 445 446def StagePatchMetadataFileForCommit(patch_metadata_file_path: str) -> None: 447 """Stages the updated patch metadata file for commit. 448 449 Args: 450 patch_metadata_file_path: The absolute path to the patch metadata file. 451 452 Raises: 453 ValueError: Failed to stage the patch metadata file for commit or 454 invalid patch metadata file. 455 """ 456 457 if not os.path.isfile(patch_metadata_file_path): 458 raise ValueError( 459 f"Invalid patch metadata file provided: {patch_metadata_file_path}" 460 ) 461 462 # Cmd to stage the patch metadata file for commit. 463 subprocess.check_output( 464 [ 465 "git", 466 "-C", 467 os.path.dirname(patch_metadata_file_path), 468 "add", 469 patch_metadata_file_path, 470 ] 471 ) 472 473 474def StagePackagesPatchResultsForCommit( 475 package_info_dict: Dict[str, patch_utils.PatchInfo], 476 commit_messages: List[str], 477) -> List[str]: 478 """Stages the patch results of the packages to the commit message. 479 480 Args: 481 package_info_dict: A dictionary where the key is the package name and 482 the value is a dictionary that contains information about the patches 483 of the package (key). 484 commit_messages: The commit message that has the updated ebuilds and 485 upreving information. 486 487 Returns: 488 commit_messages with new additions 489 """ 490 491 # For each package, check if any patches for that package have 492 # changed, if so, add which patches have changed to the commit 493 # message. 494 for package_name, patch_info in package_info_dict.items(): 495 if ( 496 patch_info.disabled_patches 497 or patch_info.removed_patches 498 or patch_info.modified_metadata 499 ): 500 cur_package_header = f"\nFor the package {package_name}:" 501 commit_messages.append(cur_package_header) 502 503 # Add to the commit message that the patch metadata file was modified. 504 if patch_info.modified_metadata: 505 patch_metadata_path = patch_info.modified_metadata 506 metadata_file_name = os.path.basename(patch_metadata_path) 507 commit_messages.append( 508 f"The patch metadata file {metadata_file_name} was modified" 509 ) 510 511 StagePatchMetadataFileForCommit(patch_metadata_path) 512 513 # Add each disabled patch to the commit message. 514 if patch_info.disabled_patches: 515 commit_messages.append("The following patches were disabled:") 516 517 for patch_path in patch_info.disabled_patches: 518 commit_messages.append(os.path.basename(patch_path)) 519 520 # Add each removed patch to the commit message. 521 if patch_info.removed_patches: 522 commit_messages.append("The following patches were removed:") 523 524 for patch_path in patch_info.removed_patches: 525 commit_messages.append(os.path.basename(patch_path)) 526 527 RemovePatchesFromFilesDir(patch_info.removed_patches) 528 529 return commit_messages 530 531 532def UpdatePortageManifests( 533 packages: Iterable[str], chromeos_path: Path 534) -> None: 535 """Updates portage manifest files for packages. 536 537 Args: 538 packages: A list of packages to update manifests for. 539 chromeos_path: The absolute path to the chromeos checkout. 540 541 Raises: 542 CalledProcessError: ebuild failed to update manifest. 543 """ 544 manifest_ebuilds = chroot.GetChrootEbuildPaths(chromeos_path, packages) 545 for ebuild_path in manifest_ebuilds: 546 ebuild_dir = os.path.dirname(ebuild_path) 547 subprocess_helpers.ChrootRunCommand( 548 chromeos_path, ["ebuild", ebuild_path, "manifest"] 549 ) 550 subprocess_helpers.ChrootRunCommand( 551 chromeos_path, ["git", "-C", ebuild_dir, "add", "Manifest"] 552 ) 553 554 555def UpdatePackages( 556 packages: Iterable[str], 557 manifest_packages: Iterable[str], 558 llvm_variant: LLVMVariant, 559 git_hash: str, 560 svn_version: int, 561 chroot_opts: ChrootOpts, 562 mode: Optional[failure_modes.FailureModes], 563 git_hash_source: Union[int, str], 564 extra_commit_msg_lines: Optional[Iterable[str]], 565 delete_branch: bool = True, 566 upload_changes: bool = True, 567 wip: bool = False, 568) -> Optional[git.CommitContents]: 569 """Updates an LLVM hash and uprevs the ebuild of the packages. 570 571 A temporary repo is created for the changes. The changes are 572 then uploaded for review. 573 574 Args: 575 packages: A list of all the packages that are going to be updated. 576 manifest_packages: A list of packages to update manifests for. 577 llvm_variant: The LLVM hash to update. 578 git_hash: The new git hash. 579 svn_version: The SVN-style revision number of git_hash. 580 chroot_opts: options that specify the ChromeOS chroot to use. 581 mode: The mode of the patch manager when handling an applicable patch. 582 If None is passed, the patch manager won't be invoked. 583 that failed to apply. 584 Ex. 'FailureModes.FAIL' 585 git_hash_source: The source of which git hash to use based off of. 586 Ex. 'google3', 'tot', or <version> such as 365123 587 extra_commit_msg_lines: extra lines to append to the commit message. 588 Newlines are added automatically. 589 delete_branch: Delete the git branch as a final step. 590 upload_changes: Upload the commit to gerrit as a CL. 591 wip: if True, any changes uploaded will be uploaded as 592 work-in-progress. 593 594 Returns: 595 If upload_changes is set, a git.CommitContents object. Otherwise None. 596 """ 597 portage_packages = (PortagePackage(chroot_opts, pkg) for pkg in packages) 598 chromiumos_overlay_path = ( 599 chroot_opts.chromeos_root / "src" / "third_party" / "chromiumos-overlay" 600 ) 601 branch_name = "update-" + llvm_variant.value + "-" + git_hash 602 603 commit_message_header = "llvm" 604 if llvm_variant == LLVMVariant.next: 605 commit_message_header = "llvm-next" 606 if git_hash_source in get_llvm_hash.KNOWN_HASH_SOURCES: 607 commit_message_header += ( 608 f"/{git_hash_source}: upgrade to {git_hash} (r{svn_version})" 609 ) 610 else: 611 commit_message_header += f": upgrade to {git_hash} (r{svn_version})" 612 613 commit_lines = [ 614 commit_message_header + "\n", 615 "The following packages have been updated:", 616 ] 617 618 # Holds the list of packages that are updating. 619 updated_packages: List[str] = [] 620 change_list = None 621 git.CreateBranch(chromiumos_overlay_path, branch_name) 622 try: 623 for pkg in portage_packages: 624 pkg.update(llvm_variant, git_hash, svn_version) 625 updated_packages.append(pkg.package) 626 commit_lines.append(pkg.package) 627 if manifest_packages: 628 UpdatePortageManifests(manifest_packages, chroot_opts.chromeos_root) 629 commit_lines.append("Updated manifest for:") 630 commit_lines.extend(manifest_packages) 631 EnsurePackageMaskContains(chroot_opts.chromeos_root, git_hash) 632 # Handle the patches for each package. 633 if mode is not None: 634 package_info_dict = UpdatePackagesPatchMetadataFile( 635 chroot_opts, svn_version, updated_packages, mode 636 ) 637 # Update the commit message if changes were made to a package's 638 # patches. 639 commit_lines = StagePackagesPatchResultsForCommit( 640 package_info_dict, commit_lines 641 ) 642 if extra_commit_msg_lines: 643 commit_lines.extend(extra_commit_msg_lines) 644 git.CommitChanges(chromiumos_overlay_path, commit_lines) 645 if upload_changes: 646 change_list = git.UploadChanges( 647 chromiumos_overlay_path, 648 branch_name, 649 wip=wip, 650 ) 651 finally: 652 if delete_branch: 653 git.DeleteBranch(chromiumos_overlay_path, branch_name) 654 else: 655 print(f"Not deleting branch {branch_name}") 656 return change_list 657 658 659def EnsurePackageMaskContains( 660 chromeos_path: Union[Path, str], git_hash: str 661) -> None: 662 """Adds the major version of llvm to package.mask if not already present. 663 664 Args: 665 chromeos_path: The absolute path to the chromeos checkout. 666 git_hash: The new git hash. 667 668 Raises: 669 FileExistsError: package.mask not found in ../../chromiumos-overlay 670 """ 671 672 llvm_major_version = get_llvm_hash.GetLLVMMajorVersion(git_hash) 673 674 overlay_dir = os.path.join( 675 chromeos_path, "src/third_party/chromiumos-overlay" 676 ) 677 mask_path = os.path.join( 678 overlay_dir, "profiles/targets/chromeos/package.mask" 679 ) 680 with open(mask_path, "r+", encoding="utf-8") as mask_file: 681 mask_contents = mask_file.read() 682 expected_line = f"=sys-devel/llvm-{llvm_major_version}.0_pre*\n" 683 if expected_line not in mask_contents: 684 mask_file.write(expected_line) 685 686 subprocess.check_output(["git", "-C", overlay_dir, "add", mask_path]) 687 688 689def UpdatePackagesPatchMetadataFile( 690 chroot_opts: ChrootOpts, 691 svn_version: int, 692 packages: Iterable[str], 693 mode: failure_modes.FailureModes, 694) -> Dict[str, patch_utils.PatchInfo]: 695 """Updates the packages metadata file. 696 697 Args: 698 chroot_opts: options that specify the ChromeOS chroot to use. 699 svn_version: The version to use for patch management. 700 packages: All the packages to update their patch metadata file. 701 mode: The mode for the patch manager to use when an applicable patch 702 fails to apply. 703 Ex: 'FailureModes.FAIL' 704 705 Returns: 706 A dictionary where the key is the package name and the value is a 707 dictionary that has information on the patches. 708 """ 709 710 # A dictionary where the key is the package name and the value is a 711 # dictionary that has information on the patches. 712 package_info: Dict[str, patch_utils.PatchInfo] = {} 713 714 llvm_hash = get_llvm_hash.LLVMHash() 715 716 with llvm_hash.CreateTempDirectory() as temp_dir: 717 with get_llvm_hash.CreateTempLLVMRepo(temp_dir) as dirname: 718 # Ensure that 'svn_version' exists in the chromiumum mirror of 719 # LLVM by finding its corresponding git hash. 720 git_hash = get_llvm_hash.GetGitHashFrom(dirname, svn_version) 721 move_head_cmd = ["git", "-C", dirname, "checkout", git_hash, "-q"] 722 subprocess.run(move_head_cmd, stdout=subprocess.DEVNULL, check=True) 723 724 for cur_package in packages: 725 # Get the absolute path to $FILESDIR of the package. 726 chroot_ebuild_str = subprocess_helpers.ChrootRunCommand( 727 chroot_opts.chromeos_root, 728 ["equery", "w", cur_package], 729 chroot_name=chroot_opts.chroot_name, 730 out_name=chroot_opts.out_name, 731 ).strip() 732 if not chroot_ebuild_str: 733 raise RuntimeError( 734 f"could not find ebuild for {cur_package}" 735 ) 736 chroot_ebuild_path = Path( 737 chroot.ConvertChrootPathsToAbsolutePaths( 738 str(chroot_opts.chromeos_root), [chroot_ebuild_str] 739 )[0] 740 ) 741 patches_json_fp = ( 742 chroot_ebuild_path.parent / "files" / "PATCHES.json" 743 ) 744 if not patches_json_fp.is_file(): 745 raise RuntimeError( 746 f"patches file {patches_json_fp} is not a file" 747 ) 748 749 src_path = Path(dirname) 750 with patch_utils.git_clean_context(src_path): 751 if mode in ( 752 failure_modes.FailureModes.FAIL, 753 failure_modes.FailureModes.CONTINUE, 754 ): 755 patches_info = patch_utils.apply_all_from_json( 756 svn_version=svn_version, 757 llvm_src_dir=src_path, 758 patches_json_fp=patches_json_fp, 759 continue_on_failure=mode 760 == failure_modes.FailureModes.CONTINUE, 761 ) 762 elif mode == failure_modes.FailureModes.DISABLE_PATCHES: 763 patches_info = patch_utils.update_version_ranges( 764 svn_version, src_path, patches_json_fp 765 ) 766 else: 767 raise RuntimeError(f"unsupported failure mode: {mode}") 768 769 package_info[cur_package] = patches_info 770 771 return package_info 772 773 774def ChangeRepoManifest( 775 git_hash: str, 776 src_tree: Path, 777 extra_commit_msg_lines: Optional[Iterable[str]] = None, 778 delete_branch=True, 779 upload_changes=True, 780): 781 """Change the repo internal manifest for llvm-project. 782 783 Args: 784 git_hash: The LLVM git hash to change to. 785 src_tree: ChromiumOS source tree checkout. 786 extra_commit_msg_lines: Lines to append to the commit message. 787 delete_branch: Delete the branch as a final step. 788 upload_changes: Upload the changes to gerrit. 789 790 Returns: 791 The uploaded changelist CommitContents. 792 """ 793 manifest_dir = manifest_utils.get_chromeos_manifest_path(src_tree).parent 794 branch_name = "update-llvm-project-" + git_hash 795 commit_lines = ( 796 textwrap.dedent( 797 f""" 798 manifest: Update llvm-project to {git_hash} 799 800 Upgrade the local LLVM revision to match the new llvm ebuild 801 hash. This must be merged along with any chromiumos-overlay 802 changes to LLVM. Automatic uprevs rely on the manifest hash 803 to match what is specified by LLVM_HASH. 804 805 This CL is generated by the update_chromeos_llvm_hash.py script. 806 807 BUG=None 808 TEST=CQ 809 """ 810 ) 811 .lstrip() 812 .splitlines() 813 ) 814 815 change_list = None 816 git.CreateBranch(manifest_dir, branch_name) 817 try: 818 manifest_path = manifest_utils.update_chromeos_manifest( 819 git_hash, 820 src_tree, 821 ) 822 subprocess.run( 823 ["git", "-C", manifest_dir, "add", manifest_path.name], check=True 824 ) 825 if extra_commit_msg_lines: 826 commit_lines.extend(extra_commit_msg_lines) 827 git.CommitChanges(manifest_dir, commit_lines) 828 if upload_changes: 829 change_list = git.UploadChanges(manifest_dir, branch_name) 830 finally: 831 if delete_branch: 832 git.DeleteBranch(manifest_dir, branch_name) 833 else: 834 print(f"Not deleting branch {branch_name}") 835 return change_list 836 837 838def main(): 839 """Updates the LLVM next hash for each package. 840 841 Raises: 842 AssertionError: The script was run inside the chroot. 843 """ 844 845 chroot.VerifyOutsideChroot() 846 847 args_output = GetCommandLineArgs() 848 849 chroot.VerifyChromeOSRoot(args_output.chromeos_path) 850 851 llvm_variant = LLVMVariant.current 852 if args_output.is_llvm_next: 853 llvm_variant = LLVMVariant.next 854 855 git_hash_source = args_output.llvm_version 856 857 git_hash, svn_version = get_llvm_hash.GetLLVMHashAndVersionFromSVNOption( 858 git_hash_source 859 ) 860 # Filter out empty strings. For example "".split{",") returns [""]. 861 packages = set(p for p in args_output.update_packages.split(",") if p) 862 manifest_packages = set( 863 p for p in args_output.manifest_packages.split(",") if p 864 ) 865 if not manifest_packages and not args_output.is_llvm_next: 866 # Set default manifest packages only for the current llvm. 867 manifest_packages = set(DEFAULT_MANIFEST_PACKAGES) 868 869 if args_output.no_patching: 870 patch_update_mode = None 871 else: 872 patch_update_mode = failure_modes.FailureModes(args_output.failure_mode) 873 874 change_list = UpdatePackages( 875 packages=packages, 876 manifest_packages=manifest_packages, 877 llvm_variant=llvm_variant, 878 git_hash=git_hash, 879 svn_version=svn_version, 880 chroot_opts=ChrootOpts(args_output.chromeos_path), 881 mode=patch_update_mode, 882 git_hash_source=git_hash_source, 883 extra_commit_msg_lines=None, 884 delete_branch=not args_output.no_delete_branch, 885 upload_changes=not args_output.no_upload_changes, 886 ) 887 if change_list: 888 print(f"Successfully updated packages to {git_hash} ({svn_version})") 889 print(f"Gerrit URL: {change_list.url}") 890 print(f"Change list number: {change_list.cl_number}") 891 else: 892 print("--no-upload passed, did not create a change list") 893 894 if args_output.repo_manifest and not args_output.is_llvm_next: 895 print( 896 f"Updating internal manifest to {git_hash} ({svn_version})...", 897 end="", 898 ) 899 cq_depend_line = ( 900 [f"Cq-Depend: chromium:{change_list.cl_number}"] 901 if change_list 902 else None 903 ) 904 change_list = ChangeRepoManifest( 905 git_hash, 906 args_output.chromeos_path, 907 extra_commit_msg_lines=cq_depend_line, 908 delete_branch=not args_output.no_delete_branch, 909 upload_changes=not args_output.no_upload_changes, 910 ) 911 print(" Done!") 912 if change_list: 913 print("New repo manifest CL:") 914 print(f" URL: {change_list.url}") 915 print(f" CL Number: {change_list.cl_number}") 916 else: 917 print("--no-upload passed, did not create a change list") 918 919 920if __name__ == "__main__": 921 main() 922