aboutsummaryrefslogtreecommitdiff
path: root/llvm_tools/patch_utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'llvm_tools/patch_utils.py')
-rw-r--r--llvm_tools/patch_utils.py146
1 files changed, 79 insertions, 67 deletions
diff --git a/llvm_tools/patch_utils.py b/llvm_tools/patch_utils.py
index b21cf314..c180a87a 100644
--- a/llvm_tools/patch_utils.py
+++ b/llvm_tools/patch_utils.py
@@ -34,6 +34,15 @@ HUNK_HEADER_RE = re.compile(r"^@@\s+-(\d+),(\d+)\s+\+(\d+),(\d+)\s+@@")
HUNK_END_RE = re.compile(r"^--\s*$")
PATCH_SUBFILE_HEADER_RE = re.compile(r"^\+\+\+ [ab]/(.*)$")
+CHROMEOS_PATCHES_JSON_PACKAGES = (
+ "dev-util/lldb-server",
+ "sys-devel/llvm",
+ "sys-libs/compiler-rt",
+ "sys-libs/libcxx",
+ "sys-libs/llvm-libunwind",
+ "sys-libs/scudo",
+)
+
@dataclasses.dataclass
class Hunk:
@@ -149,6 +158,31 @@ class PatchResult:
return s
+def git_apply(patch_path: Path) -> List[Union[str, Path]]:
+ """Patch a patch file using 'git apply'."""
+ return ["git", "apply", patch_path]
+
+
+def git_am(patch_path: Path) -> List[Union[str, Path]]:
+ """Patch a patch file using 'git am'."""
+ return ["git", "am", "--3way", patch_path]
+
+
+def gnu_patch(root_dir: Path, patch_path: Path) -> List[Union[str, Path]]:
+ """Patch a patch file using GNU 'patch'."""
+ return [
+ "patch",
+ "-d",
+ root_dir.absolute(),
+ "-f",
+ "-E",
+ "-p1",
+ "--no-backup-if-mismatch",
+ "-i",
+ patch_path,
+ ]
+
+
@dataclasses.dataclass
class PatchEntry:
"""Object mapping of an entry of PATCHES.json."""
@@ -232,16 +266,6 @@ class PatchEntry:
until_v = sys.maxsize
return from_v <= svn_version < until_v
- def is_old(self, svn_version: int) -> bool:
- """Is this patch old compared to `svn_version`?"""
- if not self.version_range:
- return False
- until_v = self.version_range.get("until")
- # Sometimes the key is there, but it's set to None.
- if until_v is None:
- until_v = sys.maxsize
- return svn_version >= until_v
-
def apply(
self,
root_dir: Path,
@@ -256,11 +280,8 @@ class PatchEntry:
f"Cannot apply: patch {abs_patch_path} is not a file"
)
- if not patch_cmd:
- patch_cmd = gnu_patch
-
- if patch_cmd == gnu_patch:
- cmd = patch_cmd(root_dir, abs_patch_path) + (extra_args or [])
+ if not patch_cmd or patch_cmd is gnu_patch:
+ cmd = gnu_patch(root_dir, abs_patch_path) + (extra_args or [])
else:
cmd = patch_cmd(abs_patch_path) + (extra_args or [])
@@ -272,7 +293,7 @@ class PatchEntry:
parsed_hunks = self.parsed_hunks()
failed_hunks_id_dict = parse_failed_patch_output(e.stdout)
failed_hunks = {}
- if patch_cmd == gnu_patch:
+ if patch_cmd is gnu_patch:
for path, failed_hunk_ids in failed_hunks_id_dict.items():
hunks_for_file = parsed_hunks[path]
failed_hunks[path] = [
@@ -281,7 +302,7 @@ class PatchEntry:
if hunk.hunk_id in failed_hunk_ids
]
elif failed_hunks_id_dict:
- # use git am
+ # using git am
failed_hunks = parsed_hunks
return PatchResult(succeeded=False, failed_hunks=failed_hunks)
@@ -290,9 +311,19 @@ class PatchEntry:
def test_apply(
self, root_dir: Path, patch_cmd: Optional[Callable] = None
) -> PatchResult:
- """Dry run applying a patch to a given directory."""
- extra_args = [] if patch_cmd == git_am else ["--dry-run"]
- return self.apply(root_dir, patch_cmd, extra_args)
+ """Dry run applying a patch to a given directory.
+
+ When using gnu_patch, this will pass --dry-run.
+ When using git_am or git_apply, this will instead
+ use git_apply with --summary.
+ """
+ if patch_cmd is git_am or patch_cmd is git_apply:
+ # There is no dry run option for git am,
+ # so we use git apply for test.
+ return self.apply(root_dir, git_apply, ["--summary"])
+ if patch_cmd is gnu_patch or patch_cmd is None:
+ return self.apply(root_dir, patch_cmd, ["--dry-run"])
+ raise ValueError(f"No such patch command: {patch_cmd.__name__}.")
def title(self) -> str:
if not self.metadata:
@@ -300,6 +331,17 @@ class PatchEntry:
return self.metadata.get("title", "")
+def patch_applies_after(
+ version_range: Optional[Dict[str, Optional[int]]], svn_version: int
+) -> bool:
+ """Does this patch apply after `svn_version`?"""
+ if not version_range:
+ return True
+ until = version_range.get("until")
+ before_svn_version = until is not None and svn_version > until
+ return not before_svn_version
+
+
@dataclasses.dataclass(frozen=True)
class PatchInfo:
"""Holds info for a round of patch applications."""
@@ -376,6 +418,7 @@ def apply_all_from_json(
svn_version: LLVM Subversion revision to patch.
llvm_src_dir: llvm-project root-level source directory to patch.
patches_json_fp: Filepath to the PATCHES.json file.
+ patch_cmd: The function to use when actually applying the patch.
continue_on_failure: Skip any patches which failed to apply,
rather than throw an Exception.
"""
@@ -565,6 +608,7 @@ def update_version_ranges_with_entries(
svn_version: LLVM revision number.
llvm_src_dir: llvm-project directory path.
patch_entries: PatchEntry objects to modify.
+ patch_cmd: The function to use when actually applying the patch.
Returns:
Tuple of (modified entries, applied patches)
@@ -597,9 +641,7 @@ def update_version_ranges_with_entries(
return modified_entries, applied_patches
-def remove_old_patches(
- svn_version: int, llvm_src_dir: Path, patches_json_fp: Path
-) -> PatchInfo:
+def remove_old_patches(svn_version: int, patches_json: Path) -> List[Path]:
"""Remove patches that don't and will never apply for the future.
Patches are determined to be "old" via the "is_old" method for
@@ -607,54 +649,24 @@ def remove_old_patches(
Args:
svn_version: LLVM SVN version.
- llvm_src_dir: LLVM source directory.
- patches_json_fp: Location to edit patches on.
+ patches_json: Location of PATCHES.json.
Returns:
- PatchInfo for modified patches.
+ A list of all patch paths removed from PATCHES.json.
"""
- with patches_json_fp.open(encoding="utf-8") as f:
- contents = f.read()
+ contents = patches_json.read_text(encoding="utf-8")
indent_len = predict_indent(contents.splitlines())
- patch_entries = json_str_to_patch_entries(
- llvm_src_dir,
- contents,
- )
- oldness = [(entry, entry.is_old(svn_version)) for entry in patch_entries]
- filtered_entries = [entry.to_dict() for entry, old in oldness if not old]
- with atomic_write_file.atomic_write(patches_json_fp, encoding="utf-8") as f:
- _write_json_changes(filtered_entries, f, indent_len=indent_len)
- removed_entries = [entry for entry, old in oldness if old]
- plural_patches = "patch" if len(removed_entries) == 1 else "patches"
- print(f"Removed {len(removed_entries)} old {plural_patches}:")
- for r in removed_entries:
- print(f"- {r.rel_patch_path}: {r.title()}")
- return PatchInfo(
- non_applicable_patches=[],
- applied_patches=[],
- failed_patches=[],
- disabled_patches=[],
- removed_patches=[p.rel_patch_path for p in removed_entries],
- modified_metadata=str(patches_json_fp) if removed_entries else None,
- )
-
-
-def git_am(patch_path: Path) -> List[Union[str, Path]]:
- cmd = ["git", "am", "--3way", str(patch_path)]
- return cmd
+ still_new = []
+ removed_patches = []
+ patches_parent = patches_json.parent
+ for entry in json.loads(contents):
+ if patch_applies_after(entry.get("version_range"), svn_version):
+ still_new.append(entry)
+ else:
+ removed_patches.append(patches_parent / entry["rel_patch_path"])
+ with atomic_write_file.atomic_write(patches_json, encoding="utf-8") as f:
+ _write_json_changes(still_new, f, indent_len=indent_len)
-def gnu_patch(root_dir: Path, patch_path: Path) -> List[Union[str, Path]]:
- cmd = [
- "patch",
- "-d",
- str(root_dir.absolute()),
- "-f",
- "-E",
- "-p1",
- "--no-backup-if-mismatch",
- "-i",
- str(patch_path),
- ]
- return cmd
+ return removed_patches