xref: /aosp_15_r20/external/toolchain-utils/llvm_tools/get_upstream_patch.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1*760c253cSXin Li#!/usr/bin/env python3
2*760c253cSXin Li# Copyright 2020 The ChromiumOS Authors
3*760c253cSXin Li# Use of this source code is governed by a BSD-style license that can be
4*760c253cSXin Li# found in the LICENSE file.
5*760c253cSXin Li
6*760c253cSXin Li"""Get an upstream patch to LLVM's PATCHES.json."""
7*760c253cSXin Li
8*760c253cSXin Liimport argparse
9*760c253cSXin Liimport dataclasses
10*760c253cSXin Liimport datetime
11*760c253cSXin Liimport json
12*760c253cSXin Liimport logging
13*760c253cSXin Liimport os
14*760c253cSXin Lifrom pathlib import Path
15*760c253cSXin Liimport subprocess
16*760c253cSXin Liimport sys
17*760c253cSXin Liimport typing as t
18*760c253cSXin Li
19*760c253cSXin Liimport chroot
20*760c253cSXin Liimport get_llvm_hash
21*760c253cSXin Liimport git
22*760c253cSXin Liimport git_llvm_rev
23*760c253cSXin Liimport patch_utils
24*760c253cSXin Li
25*760c253cSXin Li
26*760c253cSXin Li__DOC_EPILOGUE = """
27*760c253cSXin LiExample Usage:
28*760c253cSXin Li  get_upstream_patch --chromeos_path ~/chromiumos --platform chromiumos \
29*760c253cSXin Li--sha 1234567 --sha 890abdc
30*760c253cSXin Li"""
31*760c253cSXin Li
32*760c253cSXin Li
33*760c253cSXin Liclass CherrypickError(ValueError):
34*760c253cSXin Li    """A ValueError that highlights the cherry-pick has been seen before"""
35*760c253cSXin Li
36*760c253cSXin Li
37*760c253cSXin Liclass CherrypickVersionError(ValueError):
38*760c253cSXin Li    """A ValueError that highlights the cherry-pick is before the start_sha"""
39*760c253cSXin Li
40*760c253cSXin Li
41*760c253cSXin Liclass PatchApplicationError(ValueError):
42*760c253cSXin Li    """A ValueError indicating that a test patch application was unsuccessful"""
43*760c253cSXin Li
44*760c253cSXin Li
45*760c253cSXin Lidef validate_patch_application(
46*760c253cSXin Li    llvm_dir: Path, svn_version: int, patches_json_fp: Path, patch_props
47*760c253cSXin Li):
48*760c253cSXin Li    start_sha = get_llvm_hash.GetGitHashFrom(llvm_dir, svn_version)
49*760c253cSXin Li    subprocess.run(["git", "-C", llvm_dir, "checkout", start_sha], check=True)
50*760c253cSXin Li
51*760c253cSXin Li    predecessor_apply_results = patch_utils.apply_all_from_json(
52*760c253cSXin Li        svn_version, llvm_dir, patches_json_fp, continue_on_failure=True
53*760c253cSXin Li    )
54*760c253cSXin Li
55*760c253cSXin Li    if predecessor_apply_results.failed_patches:
56*760c253cSXin Li        logging.error("Failed to apply patches from PATCHES.json:")
57*760c253cSXin Li        for p in predecessor_apply_results.failed_patches:
58*760c253cSXin Li            logging.error("Patch title: %s", p.title())
59*760c253cSXin Li        raise PatchApplicationError("Failed to apply patch from PATCHES.json")
60*760c253cSXin Li
61*760c253cSXin Li    patch_entry = patch_utils.PatchEntry.from_dict(
62*760c253cSXin Li        patches_json_fp.parent, patch_props
63*760c253cSXin Li    )
64*760c253cSXin Li    test_apply_result = patch_entry.test_apply(Path(llvm_dir))
65*760c253cSXin Li
66*760c253cSXin Li    if not test_apply_result:
67*760c253cSXin Li        logging.error("Could not apply requested patch")
68*760c253cSXin Li        logging.error(test_apply_result.failure_info())
69*760c253cSXin Li        raise PatchApplicationError(
70*760c253cSXin Li            f'Failed to apply patch: {patch_props["metadata"]["title"]}'
71*760c253cSXin Li        )
72*760c253cSXin Li
73*760c253cSXin Li
74*760c253cSXin Lidef add_patch(
75*760c253cSXin Li    patches_json_path: str,
76*760c253cSXin Li    patches_dir: str,
77*760c253cSXin Li    relative_patches_dir: str,
78*760c253cSXin Li    start_version: git_llvm_rev.Rev,
79*760c253cSXin Li    llvm_dir: t.Union[Path, str],
80*760c253cSXin Li    rev: t.Union[git_llvm_rev.Rev, str],
81*760c253cSXin Li    sha: str,
82*760c253cSXin Li    package: str,
83*760c253cSXin Li    platforms: t.Iterable[str],
84*760c253cSXin Li):
85*760c253cSXin Li    """Gets the start and end intervals in 'json_file'.
86*760c253cSXin Li
87*760c253cSXin Li    Args:
88*760c253cSXin Li        patches_json_path: The absolute path to PATCHES.json.
89*760c253cSXin Li        patches_dir: The aboslute path to the directory patches are in.
90*760c253cSXin Li        relative_patches_dir: The relative path to PATCHES.json.
91*760c253cSXin Li        start_version: The base LLVM revision this patch applies to.
92*760c253cSXin Li        llvm_dir: The path to LLVM checkout.
93*760c253cSXin Li        rev: An LLVM revision (git_llvm_rev.Rev) for a cherrypicking, or a
94*760c253cSXin Li        differential revision (str) otherwise.
95*760c253cSXin Li        sha: The LLVM git sha that corresponds to the patch. For differential
96*760c253cSXin Li        revisions, the git sha from  the local commit created by 'arc patch'
97*760c253cSXin Li        is used.
98*760c253cSXin Li        package: The LLVM project name this patch applies to.
99*760c253cSXin Li        platforms: List of platforms this patch applies to.
100*760c253cSXin Li
101*760c253cSXin Li    Raises:
102*760c253cSXin Li        CherrypickError: A ValueError that highlights the cherry-pick has been
103*760c253cSXin Li        seen before.
104*760c253cSXin Li        CherrypickRangeError: A ValueError that's raised when the given patch
105*760c253cSXin Li        is from before the start_sha.
106*760c253cSXin Li    """
107*760c253cSXin Li
108*760c253cSXin Li    is_cherrypick = isinstance(rev, git_llvm_rev.Rev)
109*760c253cSXin Li    if is_cherrypick:
110*760c253cSXin Li        file_name = f"{sha}.patch"
111*760c253cSXin Li    else:
112*760c253cSXin Li        file_name = f"{rev}.patch"
113*760c253cSXin Li    rel_patch_path = os.path.join(relative_patches_dir, file_name)
114*760c253cSXin Li
115*760c253cSXin Li    # Check that we haven't grabbed a patch range that's nonsensical.
116*760c253cSXin Li    end_vers = rev.number if isinstance(rev, git_llvm_rev.Rev) else None
117*760c253cSXin Li    if end_vers is not None and end_vers <= start_version.number:
118*760c253cSXin Li        raise CherrypickVersionError(
119*760c253cSXin Li            f"`until` version {end_vers} is earlier or equal to"
120*760c253cSXin Li            f" `from` version {start_version.number} for patch"
121*760c253cSXin Li            f" {rel_patch_path}"
122*760c253cSXin Li        )
123*760c253cSXin Li
124*760c253cSXin Li    with open(patches_json_path, encoding="utf-8") as f:
125*760c253cSXin Li        contents = f.read()
126*760c253cSXin Li    indent_len = patch_utils.predict_indent(contents.splitlines())
127*760c253cSXin Li    patches_json = json.loads(contents)
128*760c253cSXin Li
129*760c253cSXin Li    for p in patches_json:
130*760c253cSXin Li        rel_path = p["rel_patch_path"]
131*760c253cSXin Li        if rel_path == rel_patch_path:
132*760c253cSXin Li            raise CherrypickError(
133*760c253cSXin Li                f"Patch at {rel_path} already exists in PATCHES.json"
134*760c253cSXin Li            )
135*760c253cSXin Li        if is_cherrypick:
136*760c253cSXin Li            if sha in rel_path:
137*760c253cSXin Li                logging.warning(
138*760c253cSXin Li                    "Similarly-named patch already exists in PATCHES.json: %r",
139*760c253cSXin Li                    rel_path,
140*760c253cSXin Li                )
141*760c253cSXin Li
142*760c253cSXin Li    with open(os.path.join(patches_dir, file_name), "wb") as f:
143*760c253cSXin Li        cmd = ["git", "show", sha]
144*760c253cSXin Li        # Only apply the part of the patch that belongs to this package, expect
145*760c253cSXin Li        # LLVM. This is because some packages are built with LLVM ebuild on X86
146*760c253cSXin Li        # but not on the other architectures. e.g. compiler-rt. Therefore
147*760c253cSXin Li        # always apply the entire patch to LLVM ebuild as a workaround.
148*760c253cSXin Li        if package != "llvm":
149*760c253cSXin Li            cmd.append(package_to_project(package))
150*760c253cSXin Li        subprocess.check_call(cmd, stdout=f, cwd=llvm_dir)
151*760c253cSXin Li
152*760c253cSXin Li    commit_subject = subprocess.check_output(
153*760c253cSXin Li        ["git", "log", "-n1", "--format=%s", sha],
154*760c253cSXin Li        cwd=llvm_dir,
155*760c253cSXin Li        encoding="utf-8",
156*760c253cSXin Li    )
157*760c253cSXin Li    patch_props = {
158*760c253cSXin Li        "rel_patch_path": rel_patch_path,
159*760c253cSXin Li        "metadata": {
160*760c253cSXin Li            "title": commit_subject.strip(),
161*760c253cSXin Li            "info": [],
162*760c253cSXin Li        },
163*760c253cSXin Li        "platforms": sorted(platforms),
164*760c253cSXin Li        "version_range": {
165*760c253cSXin Li            "from": start_version.number,
166*760c253cSXin Li            "until": end_vers,
167*760c253cSXin Li        },
168*760c253cSXin Li    }
169*760c253cSXin Li
170*760c253cSXin Li    with patch_utils.git_clean_context(Path(llvm_dir)):
171*760c253cSXin Li        validate_patch_application(
172*760c253cSXin Li            Path(llvm_dir),
173*760c253cSXin Li            start_version.number,
174*760c253cSXin Li            Path(patches_json_path),
175*760c253cSXin Li            patch_props,
176*760c253cSXin Li        )
177*760c253cSXin Li
178*760c253cSXin Li    patches_json.append(patch_props)
179*760c253cSXin Li
180*760c253cSXin Li    temp_file = patches_json_path + ".tmp"
181*760c253cSXin Li    with open(temp_file, "w", encoding="utf-8") as f:
182*760c253cSXin Li        json.dump(
183*760c253cSXin Li            patches_json,
184*760c253cSXin Li            f,
185*760c253cSXin Li            indent=indent_len,
186*760c253cSXin Li            separators=(",", ": "),
187*760c253cSXin Li            sort_keys=True,
188*760c253cSXin Li        )
189*760c253cSXin Li        f.write("\n")
190*760c253cSXin Li    os.rename(temp_file, patches_json_path)
191*760c253cSXin Li
192*760c253cSXin Li
193*760c253cSXin Li# Resolves a git ref (or similar) to a LLVM SHA.
194*760c253cSXin Lidef resolve_llvm_ref(llvm_dir: t.Union[Path, str], sha: str) -> str:
195*760c253cSXin Li    return subprocess.check_output(
196*760c253cSXin Li        ["git", "rev-parse", sha],
197*760c253cSXin Li        encoding="utf-8",
198*760c253cSXin Li        cwd=llvm_dir,
199*760c253cSXin Li    ).strip()
200*760c253cSXin Li
201*760c253cSXin Li
202*760c253cSXin Li# Get the package name of an LLVM project
203*760c253cSXin Lidef project_to_package(project: str) -> str:
204*760c253cSXin Li    if project == "libunwind":
205*760c253cSXin Li        return "llvm-libunwind"
206*760c253cSXin Li    return project
207*760c253cSXin Li
208*760c253cSXin Li
209*760c253cSXin Li# Get the LLVM project name of a package
210*760c253cSXin Lidef package_to_project(package: str) -> str:
211*760c253cSXin Li    if package == "llvm-libunwind":
212*760c253cSXin Li        return "libunwind"
213*760c253cSXin Li    return package
214*760c253cSXin Li
215*760c253cSXin Li
216*760c253cSXin Li# Get the LLVM projects change in the specifed sha
217*760c253cSXin Lidef get_package_names(sha: str, llvm_dir: t.Union[Path, str]) -> list:
218*760c253cSXin Li    paths = subprocess.check_output(
219*760c253cSXin Li        ["git", "show", "--name-only", "--format=", sha],
220*760c253cSXin Li        cwd=llvm_dir,
221*760c253cSXin Li        encoding="utf-8",
222*760c253cSXin Li    ).splitlines()
223*760c253cSXin Li    # Some LLVM projects are built by LLVM ebuild on X86, so always apply the
224*760c253cSXin Li    # patch to LLVM ebuild
225*760c253cSXin Li    packages = {"llvm"}
226*760c253cSXin Li    # Detect if there are more packages to apply the patch to
227*760c253cSXin Li    for path in paths:
228*760c253cSXin Li        package = project_to_package(path.split("/")[0])
229*760c253cSXin Li        if package in ("compiler-rt", "libcxx", "libcxxabi", "llvm-libunwind"):
230*760c253cSXin Li            packages.add(package)
231*760c253cSXin Li    return list(sorted(packages))
232*760c253cSXin Li
233*760c253cSXin Li
234*760c253cSXin Lidef create_patch_for_packages(
235*760c253cSXin Li    packages: t.List[str],
236*760c253cSXin Li    symlinks: t.List[str],
237*760c253cSXin Li    start_rev: git_llvm_rev.Rev,
238*760c253cSXin Li    rev: t.Union[git_llvm_rev.Rev, str],
239*760c253cSXin Li    sha: str,
240*760c253cSXin Li    llvm_dir: t.Union[Path, str],
241*760c253cSXin Li    platforms: t.Iterable[str],
242*760c253cSXin Li):
243*760c253cSXin Li    """Create a patch and add its metadata for each package"""
244*760c253cSXin Li    for package, symlink in zip(packages, symlinks):
245*760c253cSXin Li        symlink_dir = os.path.dirname(symlink)
246*760c253cSXin Li        patches_json_path = os.path.join(symlink_dir, "files/PATCHES.json")
247*760c253cSXin Li        relative_patches_dir = "cherry" if package == "llvm" else ""
248*760c253cSXin Li        patches_dir = os.path.join(symlink_dir, "files", relative_patches_dir)
249*760c253cSXin Li        logging.info("Getting %s (%s) into %s", rev, sha, package)
250*760c253cSXin Li        add_patch(
251*760c253cSXin Li            patches_json_path,
252*760c253cSXin Li            patches_dir,
253*760c253cSXin Li            relative_patches_dir,
254*760c253cSXin Li            start_rev,
255*760c253cSXin Li            llvm_dir,
256*760c253cSXin Li            rev,
257*760c253cSXin Li            sha,
258*760c253cSXin Li            package,
259*760c253cSXin Li            platforms=platforms,
260*760c253cSXin Li        )
261*760c253cSXin Li
262*760c253cSXin Li
263*760c253cSXin Lidef make_cl(
264*760c253cSXin Li    llvm_symlink_dir: str,
265*760c253cSXin Li    branch: str,
266*760c253cSXin Li    commit_messages: t.List[str],
267*760c253cSXin Li    reviewers: t.Optional[t.List[str]],
268*760c253cSXin Li    cc: t.Optional[t.List[str]],
269*760c253cSXin Li):
270*760c253cSXin Li    subprocess.check_output(["git", "add", "--all"], cwd=llvm_symlink_dir)
271*760c253cSXin Li    git.CommitChanges(llvm_symlink_dir, commit_messages)
272*760c253cSXin Li    git.UploadChanges(llvm_symlink_dir, branch, reviewers, cc)
273*760c253cSXin Li    git.DeleteBranch(llvm_symlink_dir, branch)
274*760c253cSXin Li
275*760c253cSXin Li
276*760c253cSXin Lidef resolve_symbolic_sha(start_sha: str, chromeos_path: Path) -> str:
277*760c253cSXin Li    if start_sha == "llvm":
278*760c253cSXin Li        return get_llvm_hash.LLVMHash().GetCrOSCurrentLLVMHash(chromeos_path)
279*760c253cSXin Li
280*760c253cSXin Li    if start_sha == "llvm-next":
281*760c253cSXin Li        return get_llvm_hash.LLVMHash().GetCrOSLLVMNextHash()
282*760c253cSXin Li
283*760c253cSXin Li    return start_sha
284*760c253cSXin Li
285*760c253cSXin Li
286*760c253cSXin Lidef find_patches_and_make_cl(
287*760c253cSXin Li    chromeos_path: str,
288*760c253cSXin Li    patches: t.List[str],
289*760c253cSXin Li    start_rev: git_llvm_rev.Rev,
290*760c253cSXin Li    llvm_config: git_llvm_rev.LLVMConfig,
291*760c253cSXin Li    llvm_symlink_dir: str,
292*760c253cSXin Li    allow_failures: bool,
293*760c253cSXin Li    create_cl: bool,
294*760c253cSXin Li    skip_dependencies: bool,
295*760c253cSXin Li    reviewers: t.Optional[t.List[str]],
296*760c253cSXin Li    cc: t.Optional[t.List[str]],
297*760c253cSXin Li    platforms: t.Iterable[str],
298*760c253cSXin Li):
299*760c253cSXin Li    converted_patches = [
300*760c253cSXin Li        _convert_patch(llvm_config, skip_dependencies, p) for p in patches
301*760c253cSXin Li    ]
302*760c253cSXin Li    potential_duplicates = _get_duplicate_shas(converted_patches)
303*760c253cSXin Li    if potential_duplicates:
304*760c253cSXin Li        err_msg = "\n".join(
305*760c253cSXin Li            f"{a.patch} == {b.patch}" for a, b in potential_duplicates
306*760c253cSXin Li        )
307*760c253cSXin Li        raise RuntimeError(f"Found Duplicate SHAs:\n{err_msg}")
308*760c253cSXin Li
309*760c253cSXin Li    # CL Related variables, only used if `create_cl`
310*760c253cSXin Li    commit_messages = [
311*760c253cSXin Li        "llvm: get patches from upstream\n",
312*760c253cSXin Li    ]
313*760c253cSXin Li    branch = (
314*760c253cSXin Li        f'get-upstream-{datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")}'
315*760c253cSXin Li    )
316*760c253cSXin Li
317*760c253cSXin Li    if create_cl:
318*760c253cSXin Li        git.CreateBranch(llvm_symlink_dir, branch)
319*760c253cSXin Li
320*760c253cSXin Li    successes = []
321*760c253cSXin Li    failures = []
322*760c253cSXin Li    for parsed_patch in converted_patches:
323*760c253cSXin Li        # Find out the llvm projects changed in this commit
324*760c253cSXin Li        packages = get_package_names(parsed_patch.sha, llvm_config.dir)
325*760c253cSXin Li        # Find out the ebuild of the corresponding ChromeOS packages
326*760c253cSXin Li        ebuild_paths = chroot.GetChrootEbuildPaths(
327*760c253cSXin Li            chromeos_path,
328*760c253cSXin Li            [
329*760c253cSXin Li                "sys-devel/llvm" if package == "llvm" else "sys-libs/" + package
330*760c253cSXin Li                for package in packages
331*760c253cSXin Li            ],
332*760c253cSXin Li        )
333*760c253cSXin Li        ebuild_paths = chroot.ConvertChrootPathsToAbsolutePaths(
334*760c253cSXin Li            chromeos_path, ebuild_paths
335*760c253cSXin Li        )
336*760c253cSXin Li        # Create a local patch for all the affected llvm projects
337*760c253cSXin Li        try:
338*760c253cSXin Li            create_patch_for_packages(
339*760c253cSXin Li                packages,
340*760c253cSXin Li                ebuild_paths,
341*760c253cSXin Li                start_rev,
342*760c253cSXin Li                parsed_patch.rev,
343*760c253cSXin Li                parsed_patch.sha,
344*760c253cSXin Li                llvm_config.dir,
345*760c253cSXin Li                platforms=platforms,
346*760c253cSXin Li            )
347*760c253cSXin Li        except PatchApplicationError as e:
348*760c253cSXin Li            if allow_failures:
349*760c253cSXin Li                logging.warning(e)
350*760c253cSXin Li                failures.append(parsed_patch.sha)
351*760c253cSXin Li                continue
352*760c253cSXin Li            else:
353*760c253cSXin Li                raise e
354*760c253cSXin Li        successes.append(parsed_patch.sha)
355*760c253cSXin Li
356*760c253cSXin Li        if create_cl:
357*760c253cSXin Li            commit_messages.extend(
358*760c253cSXin Li                [
359*760c253cSXin Li                    parsed_patch.git_msg(),
360*760c253cSXin Li                    subprocess.check_output(
361*760c253cSXin Li                        ["git", "log", "-n1", "--oneline", parsed_patch.sha],
362*760c253cSXin Li                        cwd=llvm_config.dir,
363*760c253cSXin Li                        encoding="utf-8",
364*760c253cSXin Li                    ),
365*760c253cSXin Li                ]
366*760c253cSXin Li            )
367*760c253cSXin Li
368*760c253cSXin Li        if parsed_patch.is_differential:
369*760c253cSXin Li            subprocess.check_output(
370*760c253cSXin Li                ["git", "reset", "--hard", "HEAD^"], cwd=llvm_config.dir
371*760c253cSXin Li            )
372*760c253cSXin Li
373*760c253cSXin Li    if allow_failures:
374*760c253cSXin Li        success_list = (":\n\t" + "\n\t".join(successes)) if successes else "."
375*760c253cSXin Li        logging.info(
376*760c253cSXin Li            "Successfully applied %d patches%s", len(successes), success_list
377*760c253cSXin Li        )
378*760c253cSXin Li        failure_list = (":\n\t" + "\n\t".join(failures)) if failures else "."
379*760c253cSXin Li        logging.info(
380*760c253cSXin Li            "Failed to apply %d patches%s", len(failures), failure_list
381*760c253cSXin Li        )
382*760c253cSXin Li
383*760c253cSXin Li    if successes and create_cl:
384*760c253cSXin Li        make_cl(
385*760c253cSXin Li            llvm_symlink_dir,
386*760c253cSXin Li            branch,
387*760c253cSXin Li            commit_messages,
388*760c253cSXin Li            reviewers,
389*760c253cSXin Li            cc,
390*760c253cSXin Li        )
391*760c253cSXin Li
392*760c253cSXin Li
393*760c253cSXin Li@dataclasses.dataclass(frozen=True)
394*760c253cSXin Liclass ParsedPatch:
395*760c253cSXin Li    """Class to keep track of bundled patch info."""
396*760c253cSXin Li
397*760c253cSXin Li    patch: str
398*760c253cSXin Li    sha: str
399*760c253cSXin Li    is_differential: bool
400*760c253cSXin Li    rev: t.Union[git_llvm_rev.Rev, str]
401*760c253cSXin Li
402*760c253cSXin Li    def git_msg(self) -> str:
403*760c253cSXin Li        if self.is_differential:
404*760c253cSXin Li            return f"\n\nreviews.llvm.org/{self.patch}\n"
405*760c253cSXin Li        return f"\n\nreviews.llvm.org/rG{self.sha}\n"
406*760c253cSXin Li
407*760c253cSXin Li
408*760c253cSXin Lidef _convert_patch(
409*760c253cSXin Li    llvm_config: git_llvm_rev.LLVMConfig, skip_dependencies: bool, patch: str
410*760c253cSXin Li) -> ParsedPatch:
411*760c253cSXin Li    """Extract git revision info from a patch.
412*760c253cSXin Li
413*760c253cSXin Li    Args:
414*760c253cSXin Li        llvm_config: LLVM configuration object.
415*760c253cSXin Li        skip_dependencies: Pass --skip-dependecies for to `arc`
416*760c253cSXin Li        patch: A single patch referent string.
417*760c253cSXin Li
418*760c253cSXin Li    Returns:
419*760c253cSXin Li        A [ParsedPatch] object.
420*760c253cSXin Li    """
421*760c253cSXin Li
422*760c253cSXin Li    # git hash should only have lower-case letters
423*760c253cSXin Li    is_differential = patch.startswith("D")
424*760c253cSXin Li    if is_differential:
425*760c253cSXin Li        subprocess.check_output(
426*760c253cSXin Li            [
427*760c253cSXin Li                "arc",
428*760c253cSXin Li                "patch",
429*760c253cSXin Li                "--nobranch",
430*760c253cSXin Li                "--skip-dependencies" if skip_dependencies else "--revision",
431*760c253cSXin Li                patch,
432*760c253cSXin Li            ],
433*760c253cSXin Li            cwd=llvm_config.dir,
434*760c253cSXin Li        )
435*760c253cSXin Li        sha = resolve_llvm_ref(llvm_config.dir, "HEAD")
436*760c253cSXin Li        rev: t.Union[git_llvm_rev.Rev, str] = patch
437*760c253cSXin Li    else:
438*760c253cSXin Li        sha = resolve_llvm_ref(llvm_config.dir, patch)
439*760c253cSXin Li        rev = git_llvm_rev.translate_sha_to_rev(llvm_config, sha)
440*760c253cSXin Li    return ParsedPatch(
441*760c253cSXin Li        patch=patch, sha=sha, rev=rev, is_differential=is_differential
442*760c253cSXin Li    )
443*760c253cSXin Li
444*760c253cSXin Li
445*760c253cSXin Lidef _get_duplicate_shas(
446*760c253cSXin Li    patches: t.List[ParsedPatch],
447*760c253cSXin Li) -> t.List[t.Tuple[ParsedPatch, ParsedPatch]]:
448*760c253cSXin Li    """Return a list of Patches which have duplicate SHA's"""
449*760c253cSXin Li    return [
450*760c253cSXin Li        (left, right)
451*760c253cSXin Li        for i, left in enumerate(patches)
452*760c253cSXin Li        for right in patches[i + 1 :]
453*760c253cSXin Li        if left.sha == right.sha
454*760c253cSXin Li    ]
455*760c253cSXin Li
456*760c253cSXin Li
457*760c253cSXin Lidef get_from_upstream(
458*760c253cSXin Li    chromeos_path: str,
459*760c253cSXin Li    create_cl: bool,
460*760c253cSXin Li    start_sha: str,
461*760c253cSXin Li    patches: t.List[str],
462*760c253cSXin Li    platforms: t.Iterable[str],
463*760c253cSXin Li    allow_failures: bool = False,
464*760c253cSXin Li    skip_dependencies: bool = False,
465*760c253cSXin Li    reviewers: t.Optional[t.List[str]] = None,
466*760c253cSXin Li    cc: t.Optional[t.List[str]] = None,
467*760c253cSXin Li):
468*760c253cSXin Li    llvm_symlink = chroot.ConvertChrootPathsToAbsolutePaths(
469*760c253cSXin Li        chromeos_path,
470*760c253cSXin Li        chroot.GetChrootEbuildPaths(chromeos_path, ["sys-devel/llvm"]),
471*760c253cSXin Li    )[0]
472*760c253cSXin Li    llvm_symlink_dir = os.path.dirname(llvm_symlink)
473*760c253cSXin Li
474*760c253cSXin Li    git_status = subprocess.check_output(
475*760c253cSXin Li        ["git", "status", "-s"], cwd=llvm_symlink_dir, encoding="utf-8"
476*760c253cSXin Li    )
477*760c253cSXin Li
478*760c253cSXin Li    if git_status:
479*760c253cSXin Li        error_path = os.path.dirname(os.path.dirname(llvm_symlink_dir))
480*760c253cSXin Li        raise ValueError(f"Uncommited changes detected in {error_path}")
481*760c253cSXin Li
482*760c253cSXin Li    start_sha = resolve_symbolic_sha(start_sha, Path(chromeos_path))
483*760c253cSXin Li    logging.info("Base llvm hash == %s", start_sha)
484*760c253cSXin Li
485*760c253cSXin Li    llvm_config = git_llvm_rev.LLVMConfig(
486*760c253cSXin Li        remote="origin", dir=get_llvm_hash.GetAndUpdateLLVMProjectInLLVMTools()
487*760c253cSXin Li    )
488*760c253cSXin Li    start_sha = resolve_llvm_ref(llvm_config.dir, start_sha)
489*760c253cSXin Li
490*760c253cSXin Li    find_patches_and_make_cl(
491*760c253cSXin Li        chromeos_path=chromeos_path,
492*760c253cSXin Li        patches=patches,
493*760c253cSXin Li        platforms=platforms,
494*760c253cSXin Li        start_rev=git_llvm_rev.translate_sha_to_rev(llvm_config, start_sha),
495*760c253cSXin Li        llvm_config=llvm_config,
496*760c253cSXin Li        llvm_symlink_dir=llvm_symlink_dir,
497*760c253cSXin Li        create_cl=create_cl,
498*760c253cSXin Li        skip_dependencies=skip_dependencies,
499*760c253cSXin Li        reviewers=reviewers,
500*760c253cSXin Li        cc=cc,
501*760c253cSXin Li        allow_failures=allow_failures,
502*760c253cSXin Li    )
503*760c253cSXin Li
504*760c253cSXin Li    logging.info("Complete.")
505*760c253cSXin Li
506*760c253cSXin Li
507*760c253cSXin Lidef main():
508*760c253cSXin Li    chroot.VerifyOutsideChroot()
509*760c253cSXin Li    logging.basicConfig(
510*760c253cSXin Li        format="%(asctime)s: %(levelname)s: %(filename)s:%(lineno)d: "
511*760c253cSXin Li        "%(message)s",
512*760c253cSXin Li        level=logging.INFO,
513*760c253cSXin Li    )
514*760c253cSXin Li
515*760c253cSXin Li    parser = argparse.ArgumentParser(
516*760c253cSXin Li        description=__doc__,
517*760c253cSXin Li        formatter_class=argparse.RawDescriptionHelpFormatter,
518*760c253cSXin Li        epilog=__DOC_EPILOGUE,
519*760c253cSXin Li    )
520*760c253cSXin Li    parser.add_argument(
521*760c253cSXin Li        "--chromeos_path",
522*760c253cSXin Li        default=os.path.join(os.path.expanduser("~"), "chromiumos"),
523*760c253cSXin Li        help="the path to the chroot (default: %(default)s)",
524*760c253cSXin Li    )
525*760c253cSXin Li    parser.add_argument(
526*760c253cSXin Li        "--start_sha",
527*760c253cSXin Li        default="llvm-next",
528*760c253cSXin Li        help="LLVM SHA that the patch should start applying at. You can "
529*760c253cSXin Li        'specify "llvm" or "llvm-next", as well. Defaults to %(default)s.',
530*760c253cSXin Li    )
531*760c253cSXin Li    parser.add_argument(
532*760c253cSXin Li        "--sha",
533*760c253cSXin Li        action="append",
534*760c253cSXin Li        default=[],
535*760c253cSXin Li        help="The LLVM git SHA to cherry-pick.",
536*760c253cSXin Li    )
537*760c253cSXin Li    parser.add_argument(
538*760c253cSXin Li        "--differential",
539*760c253cSXin Li        action="append",
540*760c253cSXin Li        default=[],
541*760c253cSXin Li        help="The LLVM differential revision to apply. Example: D1234."
542*760c253cSXin Li        " Cannot be used for changes already merged upstream; use --sha"
543*760c253cSXin Li        " instead for those.",
544*760c253cSXin Li    )
545*760c253cSXin Li    parser.add_argument(
546*760c253cSXin Li        "--platform",
547*760c253cSXin Li        action="append",
548*760c253cSXin Li        required=True,
549*760c253cSXin Li        help="Apply this patch to the give platform. Common options include "
550*760c253cSXin Li        '"chromiumos" and "android". Can be specified multiple times to '
551*760c253cSXin Li        "apply to multiple platforms",
552*760c253cSXin Li    )
553*760c253cSXin Li    parser.add_argument(
554*760c253cSXin Li        "--allow_failures",
555*760c253cSXin Li        action="store_true",
556*760c253cSXin Li        help="Skip patches that fail to apply and continue.",
557*760c253cSXin Li    )
558*760c253cSXin Li    parser.add_argument(
559*760c253cSXin Li        "--create_cl",
560*760c253cSXin Li        action="store_true",
561*760c253cSXin Li        help="Automatically create a CL if specified",
562*760c253cSXin Li    )
563*760c253cSXin Li    parser.add_argument(
564*760c253cSXin Li        "--skip_dependencies",
565*760c253cSXin Li        action="store_true",
566*760c253cSXin Li        help="Skips a LLVM differential revision's dependencies. Only valid "
567*760c253cSXin Li        "when --differential appears exactly once.",
568*760c253cSXin Li    )
569*760c253cSXin Li    args = parser.parse_args()
570*760c253cSXin Li    chroot.VerifyChromeOSRoot(args.chromeos_path)
571*760c253cSXin Li
572*760c253cSXin Li    if not (args.sha or args.differential):
573*760c253cSXin Li        parser.error("--sha or --differential required")
574*760c253cSXin Li
575*760c253cSXin Li    if args.skip_dependencies and len(args.differential) != 1:
576*760c253cSXin Li        parser.error(
577*760c253cSXin Li            "--skip_dependencies is only valid when there's exactly one "
578*760c253cSXin Li            "supplied differential"
579*760c253cSXin Li        )
580*760c253cSXin Li
581*760c253cSXin Li    get_from_upstream(
582*760c253cSXin Li        chromeos_path=args.chromeos_path,
583*760c253cSXin Li        allow_failures=args.allow_failures,
584*760c253cSXin Li        create_cl=args.create_cl,
585*760c253cSXin Li        start_sha=args.start_sha,
586*760c253cSXin Li        patches=args.sha + args.differential,
587*760c253cSXin Li        skip_dependencies=args.skip_dependencies,
588*760c253cSXin Li        platforms=args.platform,
589*760c253cSXin Li    )
590*760c253cSXin Li
591*760c253cSXin Li
592*760c253cSXin Liif __name__ == "__main__":
593*760c253cSXin Li    sys.exit(main())
594