1#!/usr/bin/env python3 2# Copyright 2023 The ChromiumOS Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Reports on all reverts applied and not applied to sys-devel/llvm. 7 8Note that this is primarily intended to produce output that can be easily 9pasted into a spreadsheet (read: the ChromeOS Mage's test matrix), so output is 10in CSV format. 11""" 12 13import argparse 14import csv 15import dataclasses 16import json 17import logging 18from pathlib import Path 19import re 20import subprocess 21import sys 22from typing import List, Set, TextIO 23 24import get_upstream_patch 25import revert_checker 26 27 28@dataclasses.dataclass(frozen=True) 29class RevertInfo: 30 """Information to write about a revert.""" 31 32 revert: revert_checker.Revert 33 has_in_patches: bool 34 subject: str 35 36 37def list_upstream_cherrypicks(patches_json: Path) -> Set[str]: 38 with patches_json.open(encoding="utf-8") as f: 39 applicable_patches = [ 40 x 41 for x in json.load(f) 42 if not x.get("platforms") or "chromiumos" in x["platforms"] 43 ] 44 45 # Allow for arbitrary suffixes for patches; some have `-v2`, `_fixed`, etc. 46 sha_re = re.compile(r"cherry/([a-fA-F0-9]{40})\b.*\.patch$") 47 sha_like_patches = set() 48 for p in applicable_patches: 49 m = sha_re.match(p["rel_patch_path"]) 50 if m: 51 sha_like_patches.add(m.group(1)) 52 53 return sha_like_patches 54 55 56def fetch_commit_subject(llvm_git_dir: Path, sha: str) -> str: 57 result = subprocess.run( 58 ["git", "log", "--format=%s", "-n1", sha], 59 check=True, 60 cwd=llvm_git_dir, 61 encoding="utf-8", 62 stdin=subprocess.DEVNULL, 63 stdout=subprocess.PIPE, 64 # Don't set stderr, since that should only be written to on error (and 65 # `check=True`). 66 ) 67 return result.stdout.strip() 68 69 70def write_reverts_as_csv(write_to: TextIO, reverts: List[RevertInfo]): 71 writer = csv.writer(write_to, quoting=csv.QUOTE_ALL) 72 # Write the header. 73 writer.writerow(("SHA", "Reverted SHA", "Has Revert", "Subject")) 74 writer.writerows( 75 (x.revert.sha, x.revert.reverted_sha, x.has_in_patches, x.subject) 76 for x in reverts 77 ) 78 79 80def main(argv: List[str]): 81 logging.basicConfig( 82 format=">> %(asctime)s: %(levelname)s: %(filename)s:%(lineno)d: " 83 "%(message)s", 84 level=logging.INFO, 85 ) 86 87 my_dir = Path(__name__).resolve().parent 88 89 parser = argparse.ArgumentParser( 90 description=__doc__, 91 formatter_class=argparse.RawDescriptionHelpFormatter, 92 ) 93 parser.add_argument( 94 "-C", 95 "--git-dir", 96 default=my_dir.parent.parent / "llvm-project", 97 help="LLVM git directory to use.", 98 # Note that this is left as `type=str` because that's what 99 # `revert_checker` expects. 100 type=str, 101 ) 102 parser.add_argument( 103 "--llvm-next", action="store_true", help="Use the llvm-next hash" 104 ) 105 parser.add_argument( 106 "--llvm-dir", 107 help="Directory containing LLVM ebuilds", 108 type=Path, 109 default=my_dir.parent.parent / "chromiumos-overlay/sys-devel/llvm", 110 ) 111 parser.add_argument( 112 "--llvm-head", 113 default="cros/upstream/main", 114 help="ref to treat as 'origin/main' in the given LLVM dir.", 115 ) 116 opts = parser.parse_args(argv) 117 118 symbolic_sha = "llvm-next" if opts.llvm_next else "llvm" 119 llvm_sha = get_upstream_patch.resolve_symbolic_sha( 120 symbolic_sha, 121 opts.llvm_dir, 122 ) 123 logging.info("Resolved %r as the LLVM SHA to check.", llvm_sha) 124 125 in_tree_cherrypicks = list_upstream_cherrypicks( 126 opts.llvm_dir / "files/PATCHES.json" 127 ) 128 logging.info("Identified %d local cherrypicks.", len(in_tree_cherrypicks)) 129 130 raw_reverts = revert_checker.find_reverts( 131 opts.git_dir, 132 llvm_sha, 133 opts.llvm_head, 134 ) 135 136 llvm_dir = Path(opts.git_dir) 137 # Sort by `has_in_patches`, since that ordering is easier to visually scan. 138 # Note that `sorted` is stable, so any ordering in `find_reverts` will be 139 # preserved secondary to the `has_in_patches` ordering. Reverts not in 140 # PATCHES.json will appear earlier than those that are. 141 reverts = sorted( 142 ( 143 RevertInfo( 144 revert=revert, 145 subject=fetch_commit_subject(llvm_dir, revert.sha), 146 has_in_patches=revert.sha in in_tree_cherrypicks, 147 ) 148 for revert in raw_reverts 149 ), 150 key=lambda x: x.has_in_patches, 151 ) 152 153 print() 154 print("CSV summary of reverts:") 155 write_reverts_as_csv(sys.stdout, reverts) 156 157 158if __name__ == "__main__": 159 main(sys.argv[1:]) 160