1a1177b0bSTobias Hieta#!/usr/bin/env python3 2a1177b0bSTobias Hieta# 3bd3e8eb6STobias Hieta# ====- code-format-helper, runs code formatters from the ci or in a hook --*- python -*--==# 4a1177b0bSTobias Hieta# 5a1177b0bSTobias Hieta# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 6a1177b0bSTobias Hieta# See https://llvm.org/LICENSE.txt for license information. 7a1177b0bSTobias Hieta# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 8a1177b0bSTobias Hieta# 9bd3e8eb6STobias Hieta# ==--------------------------------------------------------------------------------------==# 10a1177b0bSTobias Hieta 11a1177b0bSTobias Hietaimport argparse 12a1177b0bSTobias Hietaimport os 1319bc2823SNuno Lopesimport re 1419bc2823SNuno Lopesimport shlex 15a1177b0bSTobias Hietaimport subprocess 16a1177b0bSTobias Hietaimport sys 17bd3e8eb6STobias Hietafrom typing import List, Optional 18a1177b0bSTobias Hieta 19bd3e8eb6STobias Hieta""" 20bd3e8eb6STobias HietaThis script is run by GitHub actions to ensure that the code in PR's conform to 21bd3e8eb6STobias Hietathe coding style of LLVM. It can also be installed as a pre-commit git hook to 22bd3e8eb6STobias Hietacheck the coding style before submitting it. The canonical source of this script 23bd3e8eb6STobias Hietais in the LLVM source tree under llvm/utils/git. 24bd3e8eb6STobias Hieta 25bd3e8eb6STobias HietaFor C/C++ code it uses clang-format and for Python code it uses darker (which 26bd3e8eb6STobias Hietain turn invokes black). 27bd3e8eb6STobias Hieta 28bd3e8eb6STobias HietaYou can learn more about the LLVM coding style on llvm.org: 29bd3e8eb6STobias Hietahttps://llvm.org/docs/CodingStandards.html 30bd3e8eb6STobias Hieta 31bd3e8eb6STobias HietaYou can install this script as a git hook by symlinking it to the .git/hooks 32bd3e8eb6STobias Hietadirectory: 33bd3e8eb6STobias Hieta 34bd3e8eb6STobias Hietaln -s $(pwd)/llvm/utils/git/code-format-helper.py .git/hooks/pre-commit 35bd3e8eb6STobias Hieta 36bd3e8eb6STobias HietaYou can control the exact path to clang-format or darker with the following 37bd3e8eb6STobias Hietaenvironment variables: $CLANG_FORMAT_PATH and $DARKER_FORMAT_PATH. 38bd3e8eb6STobias Hieta""" 39bd3e8eb6STobias Hieta 40bd3e8eb6STobias Hieta 41bd3e8eb6STobias Hietaclass FormatArgs: 42bd3e8eb6STobias Hieta start_rev: str = None 43bd3e8eb6STobias Hieta end_rev: str = None 44bd3e8eb6STobias Hieta repo: str = None 45bd3e8eb6STobias Hieta changed_files: List[str] = [] 46bd3e8eb6STobias Hieta token: str = None 47bd3e8eb6STobias Hieta verbose: bool = True 48a3a8acd9STobias Hieta issue_number: int = 0 492120f574STom Stellard write_comment_to_file: bool = False 50bd3e8eb6STobias Hieta 51bd3e8eb6STobias Hieta def __init__(self, args: argparse.Namespace = None) -> None: 52bd3e8eb6STobias Hieta if not args is None: 53bd3e8eb6STobias Hieta self.start_rev = args.start_rev 54bd3e8eb6STobias Hieta self.end_rev = args.end_rev 55bd3e8eb6STobias Hieta self.repo = args.repo 56bd3e8eb6STobias Hieta self.token = args.token 57bd3e8eb6STobias Hieta self.changed_files = args.changed_files 58a3a8acd9STobias Hieta self.issue_number = args.issue_number 592120f574STom Stellard self.write_comment_to_file = args.write_comment_to_file 60a1177b0bSTobias Hieta 61a1177b0bSTobias Hieta 62a1177b0bSTobias Hietaclass FormatHelper: 63a1177b0bSTobias Hieta COMMENT_TAG = "<!--LLVM CODE FORMAT COMMENT: {fmt}-->" 64af253043SRyan Prichard name: str 65af253043SRyan Prichard friendly_name: str 662120f574STom Stellard comment: dict = None 67a1177b0bSTobias Hieta 68a1177b0bSTobias Hieta @property 69a1177b0bSTobias Hieta def comment_tag(self) -> str: 70a1177b0bSTobias Hieta return self.COMMENT_TAG.replace("fmt", self.name) 71a1177b0bSTobias Hieta 72af253043SRyan Prichard @property 73af253043SRyan Prichard def instructions(self) -> str: 74af253043SRyan Prichard raise NotImplementedError() 75af253043SRyan Prichard 76bd3e8eb6STobias Hieta def has_tool(self) -> bool: 77bd3e8eb6STobias Hieta raise NotImplementedError() 78bd3e8eb6STobias Hieta 79bd3e8eb6STobias Hieta def format_run(self, changed_files: List[str], args: FormatArgs) -> Optional[str]: 80af253043SRyan Prichard raise NotImplementedError() 81a1177b0bSTobias Hieta 8256cadac8SRyan Prichard def pr_comment_text_for_diff(self, diff: str) -> str: 83a1177b0bSTobias Hieta return f""" 84a1177b0bSTobias Hieta:warning: {self.friendly_name}, {self.name} found issues in your code. :warning: 85a1177b0bSTobias Hieta 86a1177b0bSTobias Hieta<details> 87a1177b0bSTobias Hieta<summary> 88a1177b0bSTobias HietaYou can test this locally with the following command: 89a1177b0bSTobias Hieta</summary> 90a1177b0bSTobias Hieta 91a1177b0bSTobias Hieta``````````bash 92a1177b0bSTobias Hieta{self.instructions} 93a1177b0bSTobias Hieta`````````` 94a1177b0bSTobias Hieta 95a1177b0bSTobias Hieta</details> 96a1177b0bSTobias Hieta 97a1177b0bSTobias Hieta<details> 98a1177b0bSTobias Hieta<summary> 99a1177b0bSTobias HietaView the diff from {self.name} here. 100a1177b0bSTobias Hieta</summary> 101a1177b0bSTobias Hieta 102a1177b0bSTobias Hieta``````````diff 103a1177b0bSTobias Hieta{diff} 104a1177b0bSTobias Hieta`````````` 105a1177b0bSTobias Hieta 106a1177b0bSTobias Hieta</details> 107a1177b0bSTobias Hieta""" 108a1177b0bSTobias Hieta 109bd3e8eb6STobias Hieta # TODO: any type should be replaced with the correct github type, but it requires refactoring to 110bd3e8eb6STobias Hieta # not require the github module to be installed everywhere. 111bd3e8eb6STobias Hieta def find_comment(self, pr: any) -> any: 112a1177b0bSTobias Hieta for comment in pr.as_issue().get_comments(): 113a1177b0bSTobias Hieta if self.comment_tag in comment.body: 114a1177b0bSTobias Hieta return comment 115a1177b0bSTobias Hieta return None 116a1177b0bSTobias Hieta 117bd3e8eb6STobias Hieta def update_pr(self, comment_text: str, args: FormatArgs, create_new: bool) -> None: 118bd3e8eb6STobias Hieta import github 119bd3e8eb6STobias Hieta from github import IssueComment, PullRequest 120bd3e8eb6STobias Hieta 121a1177b0bSTobias Hieta repo = github.Github(args.token).get_repo(args.repo) 122a1177b0bSTobias Hieta pr = repo.get_issue(args.issue_number).as_pull_request() 123a1177b0bSTobias Hieta 12456cadac8SRyan Prichard comment_text = self.comment_tag + "\n\n" + comment_text 125a1177b0bSTobias Hieta 126a1177b0bSTobias Hieta existing_comment = self.find_comment(pr) 1272120f574STom Stellard 1282120f574STom Stellard if args.write_comment_to_file: 129de917dc2STom Stellard if create_new or existing_comment: 1302120f574STom Stellard self.comment = {"body": comment_text} 1312120f574STom Stellard if existing_comment: 1322120f574STom Stellard self.comment["id"] = existing_comment.id 1332120f574STom Stellard return 1342120f574STom Stellard 135a1177b0bSTobias Hieta if existing_comment: 13656cadac8SRyan Prichard existing_comment.edit(comment_text) 13756cadac8SRyan Prichard elif create_new: 13856cadac8SRyan Prichard pr.as_issue().create_comment(comment_text) 139a1177b0bSTobias Hieta 140bd3e8eb6STobias Hieta def run(self, changed_files: List[str], args: FormatArgs) -> bool: 141a81a7b99SMircea Trofin changed_files = [arg for arg in changed_files if "third-party" not in arg] 142a1177b0bSTobias Hieta diff = self.format_run(changed_files, args) 143bd3e8eb6STobias Hieta should_update_gh = args.token is not None and args.repo is not None 144bd3e8eb6STobias Hieta 14556cadac8SRyan Prichard if diff is None: 146bd3e8eb6STobias Hieta if should_update_gh: 147fc3eed1bSAiden Grossman comment_text = ( 148fc3eed1bSAiden Grossman ":white_check_mark: With the latest revision " 149fc3eed1bSAiden Grossman f"this PR passed the {self.friendly_name}." 150fc3eed1bSAiden Grossman ) 15156cadac8SRyan Prichard self.update_pr(comment_text, args, create_new=False) 15256cadac8SRyan Prichard return True 15356cadac8SRyan Prichard elif len(diff) > 0: 154bd3e8eb6STobias Hieta if should_update_gh: 15556cadac8SRyan Prichard comment_text = self.pr_comment_text_for_diff(diff) 15656cadac8SRyan Prichard self.update_pr(comment_text, args, create_new=True) 157bd3e8eb6STobias Hieta else: 158bd3e8eb6STobias Hieta print( 159fc3eed1bSAiden Grossman f"Warning: {self.friendly_name}, {self.name} detected " 160fc3eed1bSAiden Grossman "some issues with your code formatting..." 161bd3e8eb6STobias Hieta ) 162a1177b0bSTobias Hieta return False 163a1177b0bSTobias Hieta else: 16456cadac8SRyan Prichard # The formatter failed but didn't output a diff (e.g. some sort of 16556cadac8SRyan Prichard # infrastructure failure). 166fc3eed1bSAiden Grossman comment_text = ( 167fc3eed1bSAiden Grossman f":warning: The {self.friendly_name} failed without printing " 168fc3eed1bSAiden Grossman "a diff. Check the logs for stderr output. :warning:" 169fc3eed1bSAiden Grossman ) 17056cadac8SRyan Prichard self.update_pr(comment_text, args, create_new=False) 17156cadac8SRyan Prichard return False 172a1177b0bSTobias Hieta 173a1177b0bSTobias Hieta 174a1177b0bSTobias Hietaclass ClangFormatHelper(FormatHelper): 175a1177b0bSTobias Hieta name = "clang-format" 176a1177b0bSTobias Hieta friendly_name = "C/C++ code formatter" 177a1177b0bSTobias Hieta 178a1177b0bSTobias Hieta @property 179af253043SRyan Prichard def instructions(self) -> str: 180a1177b0bSTobias Hieta return " ".join(self.cf_cmd) 181a1177b0bSTobias Hieta 1823e28e1ecSLouis Dionne def should_include_extensionless_file(self, path: str) -> bool: 1833e28e1ecSLouis Dionne return path.startswith("libcxx/include") 1843e28e1ecSLouis Dionne 185bd3e8eb6STobias Hieta def filter_changed_files(self, changed_files: List[str]) -> List[str]: 186a1177b0bSTobias Hieta filtered_files = [] 187a1177b0bSTobias Hieta for path in changed_files: 188a1177b0bSTobias Hieta _, ext = os.path.splitext(path) 1893e28e1ecSLouis Dionne if ext in (".cpp", ".c", ".h", ".hpp", ".hxx", ".cxx", ".inc", ".cppm"): 190a1177b0bSTobias Hieta filtered_files.append(path) 1913e28e1ecSLouis Dionne elif ext == "" and self.should_include_extensionless_file(path): 1923e28e1ecSLouis Dionne filtered_files.append(path) 193a1177b0bSTobias Hieta return filtered_files 194a1177b0bSTobias Hieta 195bd3e8eb6STobias Hieta @property 196bd3e8eb6STobias Hieta def clang_fmt_path(self) -> str: 197bd3e8eb6STobias Hieta if "CLANG_FORMAT_PATH" in os.environ: 198bd3e8eb6STobias Hieta return os.environ["CLANG_FORMAT_PATH"] 199bd3e8eb6STobias Hieta return "git-clang-format" 200bd3e8eb6STobias Hieta 201bd3e8eb6STobias Hieta def has_tool(self) -> bool: 202bd3e8eb6STobias Hieta cmd = [self.clang_fmt_path, "-h"] 203bd3e8eb6STobias Hieta proc = None 204bd3e8eb6STobias Hieta try: 205bd3e8eb6STobias Hieta proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 206bd3e8eb6STobias Hieta except: 207bd3e8eb6STobias Hieta return False 208bd3e8eb6STobias Hieta return proc.returncode == 0 209bd3e8eb6STobias Hieta 210bd3e8eb6STobias Hieta def format_run(self, changed_files: List[str], args: FormatArgs) -> Optional[str]: 211a1177b0bSTobias Hieta cpp_files = self.filter_changed_files(changed_files) 212a1177b0bSTobias Hieta if not cpp_files: 213af253043SRyan Prichard return None 214bd3e8eb6STobias Hieta 215bd3e8eb6STobias Hieta cf_cmd = [self.clang_fmt_path, "--diff"] 216bd3e8eb6STobias Hieta 217bd3e8eb6STobias Hieta if args.start_rev and args.end_rev: 218bd3e8eb6STobias Hieta cf_cmd.append(args.start_rev) 219bd3e8eb6STobias Hieta cf_cmd.append(args.end_rev) 220bd3e8eb6STobias Hieta 221b3c450d4SLouis Dionne # Gather the extension of all modified files and pass them explicitly to git-clang-format. 222b3c450d4SLouis Dionne # This prevents git-clang-format from applying its own filtering rules on top of ours. 223b3c450d4SLouis Dionne extensions = set() 224b3c450d4SLouis Dionne for file in cpp_files: 225b3c450d4SLouis Dionne _, ext = os.path.splitext(file) 226b3c450d4SLouis Dionne extensions.add( 227b3c450d4SLouis Dionne ext.strip(".") 228b3c450d4SLouis Dionne ) # Exclude periods since git-clang-format takes extensions without them 229b3c450d4SLouis Dionne cf_cmd.append("--extensions") 230b3c450d4SLouis Dionne cf_cmd.append(",".join(extensions)) 231b3c450d4SLouis Dionne 232bd3e8eb6STobias Hieta cf_cmd.append("--") 233bd3e8eb6STobias Hieta cf_cmd += cpp_files 234bd3e8eb6STobias Hieta 235bd3e8eb6STobias Hieta if args.verbose: 236a1177b0bSTobias Hieta print(f"Running: {' '.join(cf_cmd)}") 237a1177b0bSTobias Hieta self.cf_cmd = cf_cmd 238bd3e8eb6STobias Hieta proc = subprocess.run(cf_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 23956cadac8SRyan Prichard sys.stdout.write(proc.stderr.decode("utf-8")) 240a1177b0bSTobias Hieta 24156cadac8SRyan Prichard if proc.returncode != 0: 24256cadac8SRyan Prichard # formatting needed, or the command otherwise failed 243bd3e8eb6STobias Hieta if args.verbose: 24456cadac8SRyan Prichard print(f"error: {self.name} exited with code {proc.returncode}") 24556d0e8ccSAiden Grossman # Print the diff in the log so that it is viewable there 24656d0e8ccSAiden Grossman print(proc.stdout.decode("utf-8")) 247a1177b0bSTobias Hieta return proc.stdout.decode("utf-8") 24856cadac8SRyan Prichard else: 249a1177b0bSTobias Hieta return None 250a1177b0bSTobias Hieta 251a1177b0bSTobias Hieta 252a1177b0bSTobias Hietaclass DarkerFormatHelper(FormatHelper): 253a1177b0bSTobias Hieta name = "darker" 254a1177b0bSTobias Hieta friendly_name = "Python code formatter" 255a1177b0bSTobias Hieta 256a1177b0bSTobias Hieta @property 257af253043SRyan Prichard def instructions(self) -> str: 258a1177b0bSTobias Hieta return " ".join(self.darker_cmd) 259a1177b0bSTobias Hieta 260bd3e8eb6STobias Hieta def filter_changed_files(self, changed_files: List[str]) -> List[str]: 261a1177b0bSTobias Hieta filtered_files = [] 262a1177b0bSTobias Hieta for path in changed_files: 263a1177b0bSTobias Hieta name, ext = os.path.splitext(path) 264a1177b0bSTobias Hieta if ext == ".py": 265a1177b0bSTobias Hieta filtered_files.append(path) 266a1177b0bSTobias Hieta 267a1177b0bSTobias Hieta return filtered_files 268a1177b0bSTobias Hieta 269bd3e8eb6STobias Hieta @property 270bd3e8eb6STobias Hieta def darker_fmt_path(self) -> str: 271bd3e8eb6STobias Hieta if "DARKER_FORMAT_PATH" in os.environ: 272bd3e8eb6STobias Hieta return os.environ["DARKER_FORMAT_PATH"] 273bd3e8eb6STobias Hieta return "darker" 274bd3e8eb6STobias Hieta 275bd3e8eb6STobias Hieta def has_tool(self) -> bool: 276bd3e8eb6STobias Hieta cmd = [self.darker_fmt_path, "--version"] 277bd3e8eb6STobias Hieta proc = None 278bd3e8eb6STobias Hieta try: 279bd3e8eb6STobias Hieta proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 280bd3e8eb6STobias Hieta except: 281bd3e8eb6STobias Hieta return False 282bd3e8eb6STobias Hieta return proc.returncode == 0 283bd3e8eb6STobias Hieta 284bd3e8eb6STobias Hieta def format_run(self, changed_files: List[str], args: FormatArgs) -> Optional[str]: 285a1177b0bSTobias Hieta py_files = self.filter_changed_files(changed_files) 286a1177b0bSTobias Hieta if not py_files: 287af253043SRyan Prichard return None 288a1177b0bSTobias Hieta darker_cmd = [ 289bd3e8eb6STobias Hieta self.darker_fmt_path, 290a1177b0bSTobias Hieta "--check", 291a1177b0bSTobias Hieta "--diff", 292bd3e8eb6STobias Hieta ] 293bd3e8eb6STobias Hieta if args.start_rev and args.end_rev: 294bd3e8eb6STobias Hieta darker_cmd += ["-r", f"{args.start_rev}...{args.end_rev}"] 295bd3e8eb6STobias Hieta darker_cmd += py_files 296bd3e8eb6STobias Hieta if args.verbose: 297a1177b0bSTobias Hieta print(f"Running: {' '.join(darker_cmd)}") 298a1177b0bSTobias Hieta self.darker_cmd = darker_cmd 299bd3e8eb6STobias Hieta proc = subprocess.run( 300bd3e8eb6STobias Hieta darker_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE 301bd3e8eb6STobias Hieta ) 302bd3e8eb6STobias Hieta if args.verbose: 30356cadac8SRyan Prichard sys.stdout.write(proc.stderr.decode("utf-8")) 304a1177b0bSTobias Hieta 30556cadac8SRyan Prichard if proc.returncode != 0: 30656cadac8SRyan Prichard # formatting needed, or the command otherwise failed 307bd3e8eb6STobias Hieta if args.verbose: 30856cadac8SRyan Prichard print(f"error: {self.name} exited with code {proc.returncode}") 30956d0e8ccSAiden Grossman # Print the diff in the log so that it is viewable there 31056d0e8ccSAiden Grossman print(proc.stdout.decode("utf-8")) 311a1177b0bSTobias Hieta return proc.stdout.decode("utf-8") 31256cadac8SRyan Prichard else: 31356cadac8SRyan Prichard sys.stdout.write(proc.stdout.decode("utf-8")) 314a1177b0bSTobias Hieta return None 315a1177b0bSTobias Hieta 316a1177b0bSTobias Hieta 31719bc2823SNuno Lopesclass UndefGetFormatHelper(FormatHelper): 31819bc2823SNuno Lopes name = "undef deprecator" 31919bc2823SNuno Lopes friendly_name = "undef deprecator" 32019bc2823SNuno Lopes 32119bc2823SNuno Lopes @property 32219bc2823SNuno Lopes def instructions(self) -> str: 32319bc2823SNuno Lopes return " ".join(shlex.quote(c) for c in self.cmd) 32419bc2823SNuno Lopes 32519bc2823SNuno Lopes def filter_changed_files(self, changed_files: List[str]) -> List[str]: 32619bc2823SNuno Lopes filtered_files = [] 32719bc2823SNuno Lopes for path in changed_files: 32819bc2823SNuno Lopes _, ext = os.path.splitext(path) 32919bc2823SNuno Lopes if ext in (".cpp", ".c", ".h", ".hpp", ".hxx", ".cxx", ".inc", ".cppm", ".ll"): 33019bc2823SNuno Lopes filtered_files.append(path) 33119bc2823SNuno Lopes return filtered_files 33219bc2823SNuno Lopes 33319bc2823SNuno Lopes def has_tool(self) -> bool: 33419bc2823SNuno Lopes return True 33519bc2823SNuno Lopes 33619bc2823SNuno Lopes def pr_comment_text_for_diff(self, diff: str) -> str: 33719bc2823SNuno Lopes return f""" 33819bc2823SNuno Lopes:warning: {self.name} found issues in your code. :warning: 33919bc2823SNuno Lopes 34019bc2823SNuno Lopes<details> 34119bc2823SNuno Lopes<summary> 34219bc2823SNuno LopesYou can test this locally with the following command: 34319bc2823SNuno Lopes</summary> 34419bc2823SNuno Lopes 34519bc2823SNuno Lopes``````````bash 34619bc2823SNuno Lopes{self.instructions} 34719bc2823SNuno Lopes`````````` 34819bc2823SNuno Lopes 34919bc2823SNuno Lopes</details> 35019bc2823SNuno Lopes 35119bc2823SNuno Lopes{diff} 35219bc2823SNuno Lopes""" 35319bc2823SNuno Lopes 35419bc2823SNuno Lopes def format_run(self, changed_files: List[str], args: FormatArgs) -> Optional[str]: 35519bc2823SNuno Lopes files = self.filter_changed_files(changed_files) 35619bc2823SNuno Lopes 35719bc2823SNuno Lopes # Use git to find files that have had a change in the number of undefs 35819bc2823SNuno Lopes regex = "([^a-zA-Z0-9#_-]undef[^a-zA-Z0-9_-]|UndefValue::get)" 35919bc2823SNuno Lopes cmd = ["git", "diff", "-U0", "--pickaxe-regex", "-S", regex] 36019bc2823SNuno Lopes 36119bc2823SNuno Lopes if args.start_rev and args.end_rev: 36219bc2823SNuno Lopes cmd.append(args.start_rev) 36319bc2823SNuno Lopes cmd.append(args.end_rev) 36419bc2823SNuno Lopes 36519bc2823SNuno Lopes cmd += files 36619bc2823SNuno Lopes self.cmd = cmd 36719bc2823SNuno Lopes 36819bc2823SNuno Lopes if args.verbose: 36919bc2823SNuno Lopes print(f"Running: {self.instructions}") 37019bc2823SNuno Lopes 37119bc2823SNuno Lopes proc = subprocess.run( 37219bc2823SNuno Lopes cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8" 37319bc2823SNuno Lopes ) 37419bc2823SNuno Lopes sys.stdout.write(proc.stderr) 37519bc2823SNuno Lopes stdout = proc.stdout 37619bc2823SNuno Lopes 37719bc2823SNuno Lopes files = [] 37819bc2823SNuno Lopes # Split the diff so we have one array entry per file. 37919bc2823SNuno Lopes # Each file is prefixed like: 38019bc2823SNuno Lopes # diff --git a/file b/file 38119bc2823SNuno Lopes for file in re.split("^diff --git ", stdout, 0, re.MULTILINE): 382*c84f5a9eSAiden Grossman # We skip checking in MIR files as undef is a valid token and not 383*c84f5a9eSAiden Grossman # going away. 384*c84f5a9eSAiden Grossman if file.endswith(".mir"): 385*c84f5a9eSAiden Grossman continue 38619bc2823SNuno Lopes # search for additions of undef 3870c629206SNuno Lopes if re.search(r"^[+](?!\s*#\s*).*(\bundef\b|UndefValue::get)", file, re.MULTILINE): 38819bc2823SNuno Lopes files.append(re.match("a/([^ ]+)", file.splitlines()[0])[1]) 38919bc2823SNuno Lopes 39019bc2823SNuno Lopes if not files: 39119bc2823SNuno Lopes return None 39219bc2823SNuno Lopes 39319bc2823SNuno Lopes files = "\n".join(" - " + f for f in files) 39419bc2823SNuno Lopes report = f""" 39519bc2823SNuno LopesThe following files introduce new uses of undef: 39619bc2823SNuno Lopes{files} 39719bc2823SNuno Lopes 39819bc2823SNuno Lopes[Undef](https://llvm.org/docs/LangRef.html#undefined-values) is now deprecated and should only be used in the rare cases where no replacement is possible. For example, a load of uninitialized memory yields `undef`. You should use `poison` values for placeholders instead. 39919bc2823SNuno Lopes 40019bc2823SNuno LopesIn tests, avoid using `undef` and having tests that trigger undefined behavior. If you need an operand with some unimportant value, you can add a new argument to the function and use that instead. 40119bc2823SNuno Lopes 40219bc2823SNuno LopesFor example, this is considered a bad practice: 40319bc2823SNuno Lopes```llvm 40419bc2823SNuno Lopesdefine void @fn() {{ 40519bc2823SNuno Lopes ... 40619bc2823SNuno Lopes br i1 undef, ... 40719bc2823SNuno Lopes}} 40819bc2823SNuno Lopes``` 40919bc2823SNuno Lopes 41019bc2823SNuno LopesPlease use the following instead: 41119bc2823SNuno Lopes```llvm 41219bc2823SNuno Lopesdefine void @fn(i1 %cond) {{ 41319bc2823SNuno Lopes ... 41419bc2823SNuno Lopes br i1 %cond, ... 41519bc2823SNuno Lopes}} 41619bc2823SNuno Lopes``` 41719bc2823SNuno Lopes 41819bc2823SNuno LopesPlease refer to the [Undefined Behavior Manual](https://llvm.org/docs/UndefinedBehavior.html) for more information. 41919bc2823SNuno Lopes""" 42019bc2823SNuno Lopes if args.verbose: 42119bc2823SNuno Lopes print(f"error: {self.name} failed") 42219bc2823SNuno Lopes print(report) 42319bc2823SNuno Lopes return report 42419bc2823SNuno Lopes 42519bc2823SNuno Lopes 42619bc2823SNuno LopesALL_FORMATTERS = (DarkerFormatHelper(), ClangFormatHelper(), UndefGetFormatHelper()) 427a1177b0bSTobias Hieta 428bd3e8eb6STobias Hieta 429bd3e8eb6STobias Hietadef hook_main(): 430bd3e8eb6STobias Hieta # fill out args 431bd3e8eb6STobias Hieta args = FormatArgs() 432c4aa8384SMehdi Amini args.verbose = os.getenv("FORMAT_HOOK_VERBOSE", False) 433bd3e8eb6STobias Hieta 434bd3e8eb6STobias Hieta # find the changed files 435bd3e8eb6STobias Hieta cmd = ["git", "diff", "--cached", "--name-only", "--diff-filter=d"] 436bd3e8eb6STobias Hieta proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 437bd3e8eb6STobias Hieta output = proc.stdout.decode("utf-8") 438bd3e8eb6STobias Hieta for line in output.splitlines(): 439bd3e8eb6STobias Hieta args.changed_files.append(line) 440bd3e8eb6STobias Hieta 441bd3e8eb6STobias Hieta failed_fmts = [] 442bd3e8eb6STobias Hieta for fmt in ALL_FORMATTERS: 443bd3e8eb6STobias Hieta if fmt.has_tool(): 444bd3e8eb6STobias Hieta if not fmt.run(args.changed_files, args): 445bd3e8eb6STobias Hieta failed_fmts.append(fmt.name) 4462120f574STom Stellard if fmt.comment: 4472120f574STom Stellard comments.append(fmt.comment) 448bd3e8eb6STobias Hieta else: 449bd3e8eb6STobias Hieta print(f"Couldn't find {fmt.name}, can't check " + fmt.friendly_name.lower()) 450bd3e8eb6STobias Hieta 451bd3e8eb6STobias Hieta if len(failed_fmts) > 0: 452c4aa8384SMehdi Amini print( 453c4aa8384SMehdi Amini "Pre-commit format hook failed, rerun with FORMAT_HOOK_VERBOSE=1 environment for verbose output" 454c4aa8384SMehdi Amini ) 455bd3e8eb6STobias Hieta sys.exit(1) 456bd3e8eb6STobias Hieta 457bd3e8eb6STobias Hieta sys.exit(0) 458bd3e8eb6STobias Hieta 459bd3e8eb6STobias Hieta 460a1177b0bSTobias Hietaif __name__ == "__main__": 461bd3e8eb6STobias Hieta script_path = os.path.abspath(__file__) 462bd3e8eb6STobias Hieta if ".git/hooks" in script_path: 463bd3e8eb6STobias Hieta hook_main() 464bd3e8eb6STobias Hieta sys.exit(0) 465bd3e8eb6STobias Hieta 466a1177b0bSTobias Hieta parser = argparse.ArgumentParser() 467a1177b0bSTobias Hieta parser.add_argument( 468a1177b0bSTobias Hieta "--token", type=str, required=True, help="GitHub authentiation token" 469a1177b0bSTobias Hieta ) 470a1177b0bSTobias Hieta parser.add_argument( 471a1177b0bSTobias Hieta "--repo", 472a1177b0bSTobias Hieta type=str, 473a1177b0bSTobias Hieta default=os.getenv("GITHUB_REPOSITORY", "llvm/llvm-project"), 474a1177b0bSTobias Hieta help="The GitHub repository that we are working with in the form of <owner>/<repo> (e.g. llvm/llvm-project)", 475a1177b0bSTobias Hieta ) 476a1177b0bSTobias Hieta parser.add_argument("--issue-number", type=int, required=True) 477a1177b0bSTobias Hieta parser.add_argument( 478a1177b0bSTobias Hieta "--start-rev", 479a1177b0bSTobias Hieta type=str, 480a1177b0bSTobias Hieta required=True, 481a1177b0bSTobias Hieta help="Compute changes from this revision.", 482a1177b0bSTobias Hieta ) 483a1177b0bSTobias Hieta parser.add_argument( 484a1177b0bSTobias Hieta "--end-rev", type=str, required=True, help="Compute changes to this revision" 485a1177b0bSTobias Hieta ) 486a1177b0bSTobias Hieta parser.add_argument( 487a1177b0bSTobias Hieta "--changed-files", 488a1177b0bSTobias Hieta type=str, 489a1177b0bSTobias Hieta help="Comma separated list of files that has been changed", 490a1177b0bSTobias Hieta ) 4912120f574STom Stellard parser.add_argument( 4922120f574STom Stellard "--write-comment-to-file", 4932120f574STom Stellard action="store_true", 4942120f574STom Stellard help="Don't post comments on the PR, instead write the comments and metadata a file called 'comment'", 4952120f574STom Stellard ) 496a1177b0bSTobias Hieta 497bd3e8eb6STobias Hieta args = FormatArgs(parser.parse_args()) 498a1177b0bSTobias Hieta 499a1177b0bSTobias Hieta changed_files = [] 500a1177b0bSTobias Hieta if args.changed_files: 501a1177b0bSTobias Hieta changed_files = args.changed_files.split(",") 502a1177b0bSTobias Hieta 50356cadac8SRyan Prichard failed_formatters = [] 5042120f574STom Stellard comments = [] 505a1177b0bSTobias Hieta for fmt in ALL_FORMATTERS: 506a1177b0bSTobias Hieta if not fmt.run(changed_files, args): 50756cadac8SRyan Prichard failed_formatters.append(fmt.name) 5082120f574STom Stellard if fmt.comment: 5092120f574STom Stellard comments.append(fmt.comment) 5102120f574STom Stellard 5112120f574STom Stellard if len(comments): 5122120f574STom Stellard with open("comments", "w") as f: 5132120f574STom Stellard import json 5142120f574STom Stellard 5152120f574STom Stellard json.dump(comments, f) 516a1177b0bSTobias Hieta 51756cadac8SRyan Prichard if len(failed_formatters) > 0: 51856cadac8SRyan Prichard print(f"error: some formatters failed: {' '.join(failed_formatters)}") 51956cadac8SRyan Prichard sys.exit(1) 520