• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright 2019 The ChromiumOS Authors
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 the LLVM hash and uprevs the build of the specified packages.
8
9For each package, a temporary repo is created and the changes are uploaded
10for review.
11"""
12
13import argparse
14import datetime
15import enum
16import os
17from pathlib import Path
18import re
19import subprocess
20from typing import Dict, Iterable
21
22import chroot
23import failure_modes
24import get_llvm_hash
25import git
26import patch_utils
27import subprocess_helpers
28
29
30DEFAULT_PACKAGES = [
31    "dev-util/lldb-server",
32    "sys-devel/llvm",
33    "sys-libs/compiler-rt",
34    "sys-libs/libcxx",
35    "sys-libs/llvm-libunwind",
36]
37
38DEFAULT_MANIFEST_PACKAGES = ["sys-devel/llvm"]
39
40
41# Specify which LLVM hash to update
42class LLVMVariant(enum.Enum):
43    """Represent the LLVM hash in an ebuild file to update."""
44
45    current = "LLVM_HASH"
46    next = "LLVM_NEXT_HASH"
47
48
49# If set to `True`, then the contents of `stdout` after executing a command will
50# be displayed to the terminal.
51verbose = False
52
53
54def defaultCrosRoot() -> Path:
55    """Get default location of chroot_path.
56
57    The logic assumes that the cros_root is ~/chromiumos, unless llvm_tools is
58    inside of a CrOS checkout, in which case that checkout should be used.
59
60    Returns:
61      The best guess location for the cros checkout.
62    """
63    llvm_tools_path = os.path.realpath(os.path.dirname(__file__))
64    if llvm_tools_path.endswith("src/third_party/toolchain-utils/llvm_tools"):
65        return Path(llvm_tools_path).parent.parent.parent.parent
66    return Path.home() / "chromiumos"
67
68
69def GetCommandLineArgs():
70    """Parses the command line for the optional command line arguments.
71
72    Returns:
73      The log level to use when retrieving the LLVM hash or google3 LLVM version,
74      the chroot path to use for executing chroot commands,
75      a list of a package or packages to update their LLVM next hash,
76      and the LLVM version to use when retrieving the LLVM hash.
77    """
78
79    # Create parser and add optional command-line arguments.
80    parser = argparse.ArgumentParser(
81        description="Updates the build's hash for llvm-next."
82    )
83
84    # Add argument for a specific chroot path.
85    parser.add_argument(
86        "--chroot_path",
87        type=Path,
88        default=defaultCrosRoot(),
89        help="the path to the chroot (default: %(default)s)",
90    )
91
92    # Add argument for specific builds to uprev and update their llvm-next hash.
93    parser.add_argument(
94        "--update_packages",
95        default=",".join(DEFAULT_PACKAGES),
96        help="Comma-separated ebuilds to update llvm-next hash for "
97        "(default: %(default)s)",
98    )
99
100    parser.add_argument(
101        "--manifest_packages",
102        default="",
103        help="Comma-separated ebuilds to update manifests for "
104        "(default: %(default)s)",
105    )
106
107    # Add argument for whether to display command contents to `stdout`.
108    parser.add_argument(
109        "--verbose",
110        action="store_true",
111        help="display contents of a command to the terminal "
112        "(default: %(default)s)",
113    )
114
115    # Add argument for the LLVM hash to update
116    parser.add_argument(
117        "--is_llvm_next",
118        action="store_true",
119        help="which llvm hash to update. If specified, update LLVM_NEXT_HASH. "
120        "Otherwise, update LLVM_HASH",
121    )
122
123    # Add argument for the LLVM version to use.
124    parser.add_argument(
125        "--llvm_version",
126        type=get_llvm_hash.IsSvnOption,
127        required=True,
128        help="which git hash to use. Either a svn revision, or one "
129        f"of {sorted(get_llvm_hash.KNOWN_HASH_SOURCES)}",
130    )
131
132    # Add argument for the mode of the patch management when handling patches.
133    parser.add_argument(
134        "--failure_mode",
135        default=failure_modes.FailureModes.FAIL.value,
136        choices=[
137            failure_modes.FailureModes.FAIL.value,
138            failure_modes.FailureModes.CONTINUE.value,
139            failure_modes.FailureModes.DISABLE_PATCHES.value,
140            failure_modes.FailureModes.REMOVE_PATCHES.value,
141        ],
142        help="the mode of the patch manager when handling failed patches "
143        "(default: %(default)s)",
144    )
145
146    # Add argument for the patch metadata file.
147    parser.add_argument(
148        "--patch_metadata_file",
149        default="PATCHES.json",
150        help="the .json file that has all the patches and their "
151        "metadata if applicable (default: PATCHES.json inside $FILESDIR)",
152    )
153
154    # Parse the command line.
155    args_output = parser.parse_args()
156
157    # FIXME: We shouldn't be using globals here, but until we fix it, make pylint
158    # stop complaining about it.
159    # pylint: disable=global-statement
160    global verbose
161
162    verbose = args_output.verbose
163
164    return args_output
165
166
167def GetEbuildPathsFromSymLinkPaths(symlinks):
168    """Reads the symlink(s) to get the ebuild path(s) to the package(s).
169
170    Args:
171      symlinks: A list of absolute path symlink/symlinks that point
172      to the package's ebuild.
173
174    Returns:
175      A dictionary where the key is the absolute path of the symlink and the value
176      is the absolute path to the ebuild that was read from the symlink.
177
178    Raises:
179      ValueError: Invalid symlink(s) were provided.
180    """
181
182    # A dictionary that holds:
183    #   key: absolute symlink path
184    #   value: absolute ebuild path
185    resolved_paths = {}
186
187    # Iterate through each symlink.
188    #
189    # For each symlink, check that it is a valid symlink,
190    # and then construct the ebuild path, and
191    # then add the ebuild path to the dict.
192    for cur_symlink in symlinks:
193        if not os.path.islink(cur_symlink):
194            raise ValueError(f"Invalid symlink provided: {cur_symlink}")
195
196        # Construct the absolute path to the ebuild.
197        ebuild_path = os.path.realpath(cur_symlink)
198
199        if cur_symlink not in resolved_paths:
200            resolved_paths[cur_symlink] = ebuild_path
201
202    return resolved_paths
203
204
205def UpdateEbuildLLVMHash(ebuild_path, llvm_variant, git_hash, svn_version):
206    """Updates the LLVM hash in the ebuild.
207
208    The build changes are staged for commit in the temporary repo.
209
210    Args:
211      ebuild_path: The absolute path to the ebuild.
212      llvm_variant: Which LLVM hash to update.
213      git_hash: The new git hash.
214      svn_version: The SVN-style revision number of git_hash.
215
216    Raises:
217      ValueError: Invalid ebuild path provided or failed to stage the commit
218      of the changes or failed to update the LLVM hash.
219    """
220
221    # Iterate through each ebuild.
222    #
223    # For each ebuild, read the file in
224    # advance and then create a temporary file
225    # that gets updated with the new LLVM hash
226    # and revision number and then the ebuild file
227    # gets updated to the temporary file.
228
229    if not os.path.isfile(ebuild_path):
230        raise ValueError(f"Invalid ebuild path provided: {ebuild_path}")
231
232    temp_ebuild_file = f"{ebuild_path}.temp"
233
234    with open(ebuild_path) as ebuild_file:
235        # write updates to a temporary file in case of interrupts
236        with open(temp_ebuild_file, "w") as temp_file:
237            for cur_line in ReplaceLLVMHash(
238                ebuild_file, llvm_variant, git_hash, svn_version
239            ):
240                temp_file.write(cur_line)
241    os.rename(temp_ebuild_file, ebuild_path)
242
243    # Get the path to the parent directory.
244    parent_dir = os.path.dirname(ebuild_path)
245
246    # Stage the changes.
247    subprocess.check_output(["git", "-C", parent_dir, "add", ebuild_path])
248
249
250def ReplaceLLVMHash(ebuild_lines, llvm_variant, git_hash, svn_version):
251    """Updates the LLVM git hash.
252
253    Args:
254      ebuild_lines: The contents of the ebuild file.
255      llvm_variant: The LLVM hash to update.
256      git_hash: The new git hash.
257      svn_version: The SVN-style revision number of git_hash.
258
259    Yields:
260      lines of the modified ebuild file
261    """
262    is_updated = False
263    llvm_regex = re.compile(
264        "^" + re.escape(llvm_variant.value) + '="[a-z0-9]+"'
265    )
266    for cur_line in ebuild_lines:
267        if not is_updated and llvm_regex.search(cur_line):
268            # Update the git hash and revision number.
269            cur_line = f'{llvm_variant.value}="{git_hash}" # r{svn_version}\n'
270
271            is_updated = True
272
273        yield cur_line
274
275    if not is_updated:
276        raise ValueError(f"Failed to update {llvm_variant.value}")
277
278
279def UprevEbuildSymlink(symlink):
280    """Uprevs the symlink's revision number.
281
282    Increases the revision number by 1 and stages the change in
283    the temporary repo.
284
285    Args:
286      symlink: The absolute path of an ebuild symlink.
287
288    Raises:
289      ValueError: Failed to uprev the symlink or failed to stage the changes.
290    """
291
292    if not os.path.islink(symlink):
293        raise ValueError(f"Invalid symlink provided: {symlink}")
294
295    new_symlink, is_changed = re.subn(
296        r"r([0-9]+).ebuild",
297        lambda match: "r%s.ebuild" % str(int(match.group(1)) + 1),
298        symlink,
299        count=1,
300    )
301
302    if not is_changed:
303        raise ValueError("Failed to uprev the symlink.")
304
305    # rename the symlink
306    subprocess.check_output(
307        ["git", "-C", os.path.dirname(symlink), "mv", symlink, new_symlink]
308    )
309
310
311def UprevEbuildToVersion(symlink, svn_version, git_hash):
312    """Uprevs the ebuild's revision number.
313
314    Increases the revision number by 1 and stages the change in
315    the temporary repo.
316
317    Args:
318      symlink: The absolute path of an ebuild symlink.
319      svn_version: The SVN-style revision number of git_hash.
320      git_hash: The new git hash.
321
322    Raises:
323      ValueError: Failed to uprev the ebuild or failed to stage the changes.
324      AssertionError: No llvm version provided for an LLVM uprev
325    """
326
327    if not os.path.islink(symlink):
328        raise ValueError(f"Invalid symlink provided: {symlink}")
329
330    ebuild = os.path.realpath(symlink)
331    llvm_major_version = get_llvm_hash.GetLLVMMajorVersion(git_hash)
332    # llvm
333    package = os.path.basename(os.path.dirname(symlink))
334    if not package:
335        raise ValueError("Tried to uprev an unknown package")
336    if package == "llvm":
337        new_ebuild, is_changed = re.subn(
338            r"(\d+)\.(\d+)_pre([0-9]+)_p([0-9]+)",
339            "%s.\\2_pre%s_p%s"
340            % (
341                llvm_major_version,
342                svn_version,
343                datetime.datetime.today().strftime("%Y%m%d"),
344            ),
345            ebuild,
346            count=1,
347        )
348    # any other package
349    else:
350        new_ebuild, is_changed = re.subn(
351            r"(\d+)\.(\d+)_pre([0-9]+)",
352            "%s.\\2_pre%s" % (llvm_major_version, svn_version),
353            ebuild,
354            count=1,
355        )
356
357    if not is_changed:  # failed to increment the revision number
358        raise ValueError("Failed to uprev the ebuild.")
359
360    symlink_dir = os.path.dirname(symlink)
361
362    # Rename the ebuild
363    subprocess.check_output(
364        ["git", "-C", symlink_dir, "mv", ebuild, new_ebuild]
365    )
366
367    # Create a symlink of the renamed ebuild
368    new_symlink = new_ebuild[: -len(".ebuild")] + "-r1.ebuild"
369    subprocess.check_output(["ln", "-s", "-r", new_ebuild, new_symlink])
370
371    if not os.path.islink(new_symlink):
372        raise ValueError(
373            f'Invalid symlink name: {new_ebuild[:-len(".ebuild")]}'
374        )
375
376    subprocess.check_output(["git", "-C", symlink_dir, "add", new_symlink])
377
378    # Remove the old symlink
379    subprocess.check_output(["git", "-C", symlink_dir, "rm", symlink])
380
381
382def CreatePathDictionaryFromPackages(chroot_path, update_packages):
383    """Creates a symlink and ebuild path pair dictionary from the packages.
384
385    Args:
386      chroot_path: The absolute path to the chroot.
387      update_packages: The filtered packages to be updated.
388
389    Returns:
390      A dictionary where the key is the absolute path to the symlink
391      of the package and the value is the absolute path to the ebuild of
392      the package.
393    """
394
395    # Construct a list containing the chroot file paths of the package(s).
396    chroot_file_paths = chroot.GetChrootEbuildPaths(
397        chroot_path, update_packages
398    )
399
400    # Construct a list containing the symlink(s) of the package(s).
401    symlink_file_paths = chroot.ConvertChrootPathsToAbsolutePaths(
402        chroot_path, chroot_file_paths
403    )
404
405    # Create a dictionary where the key is the absolute path of the symlink to
406    # the package and the value is the absolute path to the ebuild of the package.
407    return GetEbuildPathsFromSymLinkPaths(symlink_file_paths)
408
409
410def RemovePatchesFromFilesDir(patches):
411    """Removes the patches from $FILESDIR of a package.
412
413    Args:
414      patches: A list of absolute paths of patches to remove
415
416    Raises:
417      ValueError: Failed to remove a patch in $FILESDIR.
418    """
419
420    for patch in patches:
421        subprocess.check_output(
422            ["git", "-C", os.path.dirname(patch), "rm", "-f", patch]
423        )
424
425
426def StagePatchMetadataFileForCommit(patch_metadata_file_path):
427    """Stages the updated patch metadata file for commit.
428
429    Args:
430      patch_metadata_file_path: The absolute path to the patch metadata file.
431
432    Raises:
433      ValueError: Failed to stage the patch metadata file for commit or invalid
434      patch metadata file.
435    """
436
437    if not os.path.isfile(patch_metadata_file_path):
438        raise ValueError(
439            f"Invalid patch metadata file provided: {patch_metadata_file_path}"
440        )
441
442    # Cmd to stage the patch metadata file for commit.
443    subprocess.check_output(
444        [
445            "git",
446            "-C",
447            os.path.dirname(patch_metadata_file_path),
448            "add",
449            patch_metadata_file_path,
450        ]
451    )
452
453
454def StagePackagesPatchResultsForCommit(package_info_dict, commit_messages):
455    """Stages the patch results of the packages to the commit message.
456
457    Args:
458      package_info_dict: A dictionary where the key is the package name and the
459      value is a dictionary that contains information about the patches of the
460      package (key).
461      commit_messages: The commit message that has the updated ebuilds and
462      upreving information.
463
464    Returns:
465      commit_messages with new additions
466    """
467
468    # For each package, check if any patches for that package have
469    # changed, if so, add which patches have changed to the commit
470    # message.
471    for package_name, patch_info_dict in package_info_dict.items():
472        if (
473            patch_info_dict["disabled_patches"]
474            or patch_info_dict["removed_patches"]
475            or patch_info_dict["modified_metadata"]
476        ):
477            cur_package_header = f"\nFor the package {package_name}:"
478            commit_messages.append(cur_package_header)
479
480        # Add to the commit message that the patch metadata file was modified.
481        if patch_info_dict["modified_metadata"]:
482            patch_metadata_path = patch_info_dict["modified_metadata"]
483            metadata_file_name = os.path.basename(patch_metadata_path)
484            commit_messages.append(
485                f"The patch metadata file {metadata_file_name} was modified"
486            )
487
488            StagePatchMetadataFileForCommit(patch_metadata_path)
489
490        # Add each disabled patch to the commit message.
491        if patch_info_dict["disabled_patches"]:
492            commit_messages.append("The following patches were disabled:")
493
494            for patch_path in patch_info_dict["disabled_patches"]:
495                commit_messages.append(os.path.basename(patch_path))
496
497        # Add each removed patch to the commit message.
498        if patch_info_dict["removed_patches"]:
499            commit_messages.append("The following patches were removed:")
500
501            for patch_path in patch_info_dict["removed_patches"]:
502                commit_messages.append(os.path.basename(patch_path))
503
504            RemovePatchesFromFilesDir(patch_info_dict["removed_patches"])
505
506    return commit_messages
507
508
509def UpdateManifests(packages: Iterable[str], chroot_path: Path):
510    """Updates manifest files for packages.
511
512    Args:
513      packages: A list of packages to update manifests for.
514      chroot_path: The absolute path to the chroot.
515
516    Raises:
517      CalledProcessError: ebuild failed to update manifest.
518    """
519    manifest_ebuilds = chroot.GetChrootEbuildPaths(chroot_path, packages)
520    for ebuild_path in manifest_ebuilds:
521        subprocess_helpers.ChrootRunCommand(
522            chroot_path, ["ebuild", ebuild_path, "manifest"]
523        )
524
525
526def UpdatePackages(
527    packages: Iterable[str],
528    manifest_packages: Iterable[str],
529    llvm_variant,
530    git_hash,
531    svn_version,
532    chroot_path: Path,
533    mode,
534    git_hash_source,
535    extra_commit_msg,
536):
537    """Updates an LLVM hash and uprevs the ebuild of the packages.
538
539    A temporary repo is created for the changes. The changes are
540    then uploaded for review.
541
542    Args:
543      packages: A list of all the packages that are going to be updated.
544      manifest_packages: A list of packages to update manifests for.
545      llvm_variant: The LLVM hash to update.
546      git_hash: The new git hash.
547      svn_version: The SVN-style revision number of git_hash.
548      chroot_path: The absolute path to the chroot.
549      mode: The mode of the patch manager when handling an applicable patch
550      that failed to apply.
551        Ex. 'FailureModes.FAIL'
552      git_hash_source: The source of which git hash to use based off of.
553        Ex. 'google3', 'tot', or <version> such as 365123
554      extra_commit_msg: extra test to append to the commit message.
555
556    Returns:
557      A nametuple that has two (key, value) pairs, where the first pair is the
558      Gerrit commit URL and the second pair is the change list number.
559    """
560
561    # Construct a dictionary where the key is the absolute path of the symlink to
562    # the package and the value is the absolute path to the ebuild of the package.
563    paths_dict = CreatePathDictionaryFromPackages(chroot_path, packages)
564
565    repo_path = os.path.dirname(next(iter(paths_dict.values())))
566
567    branch = "update-" + llvm_variant.value + "-" + git_hash
568
569    git.CreateBranch(repo_path, branch)
570
571    try:
572        commit_message_header = "llvm"
573        if llvm_variant == LLVMVariant.next:
574            commit_message_header = "llvm-next"
575        if git_hash_source in get_llvm_hash.KNOWN_HASH_SOURCES:
576            commit_message_header += (
577                f"/{git_hash_source}: upgrade to {git_hash} (r{svn_version})"
578            )
579        else:
580            commit_message_header += f": upgrade to {git_hash} (r{svn_version})"
581
582        commit_lines = [
583            commit_message_header + "\n",
584            "The following packages have been updated:",
585        ]
586
587        # Holds the list of packages that are updating.
588        packages = []
589
590        # Iterate through the dictionary.
591        #
592        # For each iteration:
593        # 1) Update the ebuild's LLVM hash.
594        # 2) Uprev the ebuild (symlink).
595        # 3) Add the modified package to the commit message.
596        for symlink_path, ebuild_path in paths_dict.items():
597            path_to_ebuild_dir = os.path.dirname(ebuild_path)
598
599            UpdateEbuildLLVMHash(
600                ebuild_path, llvm_variant, git_hash, svn_version
601            )
602
603            if llvm_variant == LLVMVariant.current:
604                UprevEbuildToVersion(symlink_path, svn_version, git_hash)
605            else:
606                UprevEbuildSymlink(symlink_path)
607
608            cur_dir_name = os.path.basename(path_to_ebuild_dir)
609            parent_dir_name = os.path.basename(
610                os.path.dirname(path_to_ebuild_dir)
611            )
612
613            packages.append(f"{parent_dir_name}/{cur_dir_name}")
614            commit_lines.append(f"{parent_dir_name}/{cur_dir_name}")
615
616        if manifest_packages:
617            UpdateManifests(manifest_packages, chroot_path)
618            commit_lines.append("Updated manifest for:")
619            commit_lines.extend(manifest_packages)
620
621        EnsurePackageMaskContains(chroot_path, git_hash)
622
623        # Handle the patches for each package.
624        package_info_dict = UpdatePackagesPatchMetadataFile(
625            chroot_path, svn_version, packages, mode
626        )
627
628        # Update the commit message if changes were made to a package's patches.
629        commit_lines = StagePackagesPatchResultsForCommit(
630            package_info_dict, commit_lines
631        )
632
633        if extra_commit_msg:
634            commit_lines.append(extra_commit_msg)
635
636        change_list = git.UploadChanges(repo_path, branch, commit_lines)
637
638    finally:
639        git.DeleteBranch(repo_path, branch)
640
641    return change_list
642
643
644def EnsurePackageMaskContains(chroot_path, git_hash):
645    """Adds the major version of llvm to package.mask if it's not already present.
646
647    Args:
648      chroot_path: The absolute path to the chroot.
649      git_hash: The new git hash.
650
651    Raises:
652      FileExistsError: package.mask not found in ../../chromiumos-overlay
653    """
654
655    llvm_major_version = get_llvm_hash.GetLLVMMajorVersion(git_hash)
656
657    overlay_dir = os.path.join(
658        chroot_path, "src/third_party/chromiumos-overlay"
659    )
660    mask_path = os.path.join(
661        overlay_dir, "profiles/targets/chromeos/package.mask"
662    )
663    with open(mask_path, "r+") as mask_file:
664        mask_contents = mask_file.read()
665        expected_line = f"=sys-devel/llvm-{llvm_major_version}.0_pre*\n"
666        if expected_line not in mask_contents:
667            mask_file.write(expected_line)
668
669    subprocess.check_output(["git", "-C", overlay_dir, "add", mask_path])
670
671
672def UpdatePackagesPatchMetadataFile(
673    chroot_path: Path,
674    svn_version: int,
675    packages: Iterable[str],
676    mode: failure_modes.FailureModes,
677) -> Dict[str, patch_utils.PatchInfo]:
678    """Updates the packages metadata file.
679
680    Args:
681      chroot_path: The absolute path to the chroot.
682      svn_version: The version to use for patch management.
683      packages: All the packages to update their patch metadata file.
684      mode: The mode for the patch manager to use when an applicable patch
685      fails to apply.
686        Ex: 'FailureModes.FAIL'
687
688    Returns:
689      A dictionary where the key is the package name and the value is a dictionary
690      that has information on the patches.
691    """
692
693    # A dictionary where the key is the package name and the value is a dictionary
694    # that has information on the patches.
695    package_info = {}
696
697    llvm_hash = get_llvm_hash.LLVMHash()
698
699    with llvm_hash.CreateTempDirectory() as temp_dir:
700        with get_llvm_hash.CreateTempLLVMRepo(temp_dir) as dirname:
701            # Ensure that 'svn_version' exists in the chromiumum mirror of LLVM by
702            # finding its corresponding git hash.
703            git_hash = get_llvm_hash.GetGitHashFrom(dirname, svn_version)
704            move_head_cmd = ["git", "-C", dirname, "checkout", git_hash, "-q"]
705            subprocess.run(move_head_cmd, stdout=subprocess.DEVNULL, check=True)
706
707            for cur_package in packages:
708                # Get the absolute path to $FILESDIR of the package.
709                chroot_ebuild_str = subprocess_helpers.ChrootRunCommand(
710                    chroot_path, ["equery", "w", cur_package]
711                ).strip()
712                if not chroot_ebuild_str:
713                    raise RuntimeError(
714                        f"could not find ebuild for {cur_package}"
715                    )
716                chroot_ebuild_path = Path(
717                    chroot.ConvertChrootPathsToAbsolutePaths(
718                        chroot_path, [chroot_ebuild_str]
719                    )[0]
720                )
721                patches_json_fp = (
722                    chroot_ebuild_path.parent / "files" / "PATCHES.json"
723                )
724                if not patches_json_fp.is_file():
725                    raise RuntimeError(
726                        f"patches file {patches_json_fp} is not a file"
727                    )
728
729                src_path = Path(dirname)
730                with patch_utils.git_clean_context(src_path):
731                    if (
732                        mode == failure_modes.FailureModes.FAIL
733                        or mode == failure_modes.FailureModes.CONTINUE
734                    ):
735                        patches_info = patch_utils.apply_all_from_json(
736                            svn_version=svn_version,
737                            llvm_src_dir=src_path,
738                            patches_json_fp=patches_json_fp,
739                            continue_on_failure=mode
740                            == failure_modes.FailureModes.CONTINUE,
741                        )
742                    elif mode == failure_modes.FailureModes.REMOVE_PATCHES:
743                        patches_info = patch_utils.remove_old_patches(
744                            svn_version, src_path, patches_json_fp
745                        )
746                    elif mode == failure_modes.FailureModes.DISABLE_PATCHES:
747                        patches_info = patch_utils.update_version_ranges(
748                            svn_version, src_path, patches_json_fp
749                        )
750
751                package_info[cur_package] = patches_info._asdict()
752
753    return package_info
754
755
756def main():
757    """Updates the LLVM next hash for each package.
758
759    Raises:
760      AssertionError: The script was run inside the chroot.
761    """
762
763    chroot.VerifyOutsideChroot()
764
765    args_output = GetCommandLineArgs()
766
767    llvm_variant = LLVMVariant.current
768    if args_output.is_llvm_next:
769        llvm_variant = LLVMVariant.next
770
771    git_hash_source = args_output.llvm_version
772
773    git_hash, svn_version = get_llvm_hash.GetLLVMHashAndVersionFromSVNOption(
774        git_hash_source
775    )
776
777    # Filter out empty strings. For example "".split{",") returns [""].
778    packages = set(p for p in args_output.update_packages.split(",") if p)
779    manifest_packages = set(
780        p for p in args_output.manifest_packages.split(",") if p
781    )
782    if not manifest_packages and not args_output.is_llvm_next:
783        # Set default manifest packages only for the current llvm.
784        manifest_packages = set(DEFAULT_MANIFEST_PACKAGES)
785    change_list = UpdatePackages(
786        packages=packages,
787        manifest_packages=manifest_packages,
788        llvm_variant=llvm_variant,
789        git_hash=git_hash,
790        svn_version=svn_version,
791        chroot_path=args_output.chroot_path,
792        mode=failure_modes.FailureModes(args_output.failure_mode),
793        git_hash_source=git_hash_source,
794        extra_commit_msg=None,
795    )
796
797    print(f"Successfully updated packages to {git_hash} ({svn_version})")
798    print(f"Gerrit URL: {change_list.url}")
799    print(f"Change list number: {change_list.cl_number}")
800
801
802if __name__ == "__main__":
803    main()
804