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