CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
411 lines
13 KiB
Python
Executable File
411 lines
13 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
# See https://llvm.org/LICENSE.txt for license information.
|
|
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from typing import List, Optional
|
|
|
|
"""
|
|
This script is run by GitHub actions to ensure that the code in PRs properly
|
|
labels LLVM APIs with `LLVM_ABI` so as not to break the LLVM DLL build. It can
|
|
also be installed as a pre-commit git hook to check ABI annotations before
|
|
submitting. The canonical source of this script is in the LLVM source tree
|
|
under llvm/utils/git.
|
|
|
|
This script uses the idt (Interface Diff Tool) to check for missing LLVM_ABI,
|
|
LLVM_C_ABI, and DEMANGLE_ABI annotations in header files.
|
|
|
|
You can install this script as a git hook by symlinking it to the .git/hooks
|
|
directory:
|
|
|
|
ln -s $(pwd)/llvm/utils/git/ids-check-helper.py .git/hooks/pre-commit
|
|
|
|
You can control the exact path to idt and compile_commands.json with the
|
|
following environment variables: $IDT_PATH and $COMPILE_COMMANDS_PATH.
|
|
"""
|
|
|
|
|
|
class IdsCheckArgs:
|
|
start_rev: str = ""
|
|
end_rev: str = ""
|
|
changed_files: List[str] = []
|
|
idt_path: str = ""
|
|
compile_commands: str = ""
|
|
repo: str = ""
|
|
token: str = ""
|
|
issue_number: int = 0
|
|
verbose: bool = True
|
|
|
|
def __init__(self, args: argparse.Namespace) -> None:
|
|
self.start_rev = args.start_rev
|
|
self.end_rev = args.end_rev
|
|
self.changed_files = args.changed_files
|
|
self.idt_path = args.idt_path
|
|
self.compile_commands = args.compile_commands
|
|
self.repo = getattr(args, "repo", "")
|
|
self.token = getattr(args, "token", "")
|
|
self.issue_number = getattr(args, "issue_number", 0)
|
|
self.verbose = getattr(args, "verbose", True)
|
|
|
|
|
|
class IdsChecker:
|
|
"""
|
|
Checker for LLVM ABI annotations using the idt tool.
|
|
"""
|
|
|
|
COMMENT_TAG = "<!--LLVM IDS CHECK COMMENT-->"
|
|
name = "ids-check"
|
|
friendly_name = "LLVM ABI annotation checker"
|
|
comment: dict = {}
|
|
|
|
# Macro definition used for all export macros
|
|
MACRO_DEFINITION = '__attribute__((visibility("default")))'
|
|
|
|
@property
|
|
def comment_tag(self) -> str:
|
|
return self.COMMENT_TAG
|
|
|
|
@property
|
|
def instructions(self) -> str:
|
|
# Provide basic usage instructions
|
|
return f"""git diff origin/main HEAD -- 'llvm/include/llvm/**/*.h' 'llvm/include/llvm-c/**/*.h' 'llvm/include/llvm/Demangle/**/*.h'
|
|
Then run idt on the changed files with appropriate --export-macro and --include-header flags."""
|
|
|
|
def pr_comment_text_for_diff(self, diff: str) -> str:
|
|
return f"""
|
|
:warning: {self.friendly_name}, {self.name} found issues in your code. :warning:
|
|
|
|
<details>
|
|
<summary>
|
|
You can test this locally with the following command:
|
|
</summary>
|
|
|
|
``````````bash
|
|
{self.instructions}
|
|
``````````
|
|
|
|
:warning:
|
|
The reproduction instructions above might return results for more than one PR
|
|
in a stack if you are using a stacked PR workflow. You can limit the results by
|
|
changing `origin/main` to the base branch/commit you want to compare against.
|
|
:warning:
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>
|
|
View the diff from {self.name} here.
|
|
</summary>
|
|
|
|
``````````diff
|
|
{diff}
|
|
``````````
|
|
|
|
</details>
|
|
"""
|
|
|
|
def update_pr(
|
|
self, comment_text: str, args: IdsCheckArgs, create_new: bool
|
|
) -> None:
|
|
import github
|
|
|
|
repo = github.Github(auth=github.Auth.Token(args.token)).get_repo(args.repo)
|
|
pr = repo.get_issue(args.issue_number).as_pull_request()
|
|
|
|
comment_text = self.comment_tag + "\n\n" + comment_text
|
|
|
|
existing_comment = None
|
|
for comment in pr.as_issue().get_comments():
|
|
if self.comment_tag in comment.body:
|
|
existing_comment = comment
|
|
break
|
|
|
|
if existing_comment:
|
|
self.comment = {"body": comment_text, "id": existing_comment.id}
|
|
elif create_new:
|
|
self.comment = {"body": comment_text}
|
|
|
|
# Define the file categories and their corresponding configurations
|
|
FILE_CATEGORIES = [
|
|
{
|
|
"name": "LLVM headers",
|
|
"patterns": ["llvm/include/llvm/**/*.h"],
|
|
"excludes": [
|
|
"llvm/include/llvm/Debuginfod/",
|
|
"llvm/include/llvm/Demangle/",
|
|
],
|
|
"export_macro": "LLVM_ABI",
|
|
"include_header": "llvm/Support/Compiler.h",
|
|
},
|
|
{
|
|
"name": "LLVM-C headers",
|
|
"patterns": ["llvm/include/llvm-c/**/*.h"],
|
|
"excludes": [],
|
|
"export_macro": "LLVM_C_ABI",
|
|
"include_header": "llvm-c/Visibility.h",
|
|
},
|
|
{
|
|
"name": "LLVM Demangle headers",
|
|
"patterns": ["llvm/include/llvm/Demangle/**/*.h"],
|
|
"excludes": [],
|
|
"export_macro": "DEMANGLE_ABI",
|
|
"include_header": "llvm/Demangle/Visibility.h",
|
|
},
|
|
]
|
|
|
|
def filter_files_for_category(
|
|
self, changed_files: List[str], category: dict
|
|
) -> List[str]:
|
|
"""Filter changed files based on category patterns and excludes."""
|
|
filtered = []
|
|
for path in changed_files:
|
|
# Check if file matches any pattern
|
|
matches_pattern = False
|
|
for pattern in category["patterns"]:
|
|
# Simple pattern matching for **/*.h style patterns
|
|
pattern_prefix = pattern.replace("**/*.h", "")
|
|
if path.startswith(pattern_prefix) and path.endswith(".h"):
|
|
matches_pattern = True
|
|
break
|
|
|
|
if not matches_pattern:
|
|
continue
|
|
|
|
# Check if file should be excluded
|
|
excluded = False
|
|
for exclude in category["excludes"]:
|
|
if path.startswith(exclude):
|
|
excluded = True
|
|
break
|
|
|
|
if not excluded:
|
|
filtered.append(path)
|
|
|
|
return filtered
|
|
|
|
def run_idt_on_files(
|
|
self,
|
|
files: List[str],
|
|
category: dict,
|
|
args: IdsCheckArgs,
|
|
idt_path: str,
|
|
compile_commands: str,
|
|
) -> bool:
|
|
"""Run idt tool on the given files with category-specific configuration."""
|
|
if not files:
|
|
return True
|
|
|
|
if args.verbose:
|
|
print(
|
|
f"Running idt on {len(files)} {category['name']} file(s)...",
|
|
file=sys.stderr,
|
|
)
|
|
|
|
for file in files:
|
|
cmd = [
|
|
idt_path,
|
|
"-p",
|
|
compile_commands,
|
|
"--apply-fixits",
|
|
"--inplace",
|
|
f"--export-macro={category['export_macro']}",
|
|
f"--include-header={category['include_header']}",
|
|
f"--extra-arg=-D{category['export_macro']}={self.MACRO_DEFINITION}",
|
|
"--extra-arg=-Wno-macro-redefined",
|
|
file,
|
|
]
|
|
|
|
if args.verbose:
|
|
print(f"Running: {' '.join(cmd)}", file=sys.stderr)
|
|
|
|
subprocess.run(cmd)
|
|
|
|
return True
|
|
|
|
def get_changed_files(self, args: IdsCheckArgs) -> List[str]:
|
|
"""Get list of changed files between revisions."""
|
|
if args.changed_files:
|
|
return args.changed_files
|
|
|
|
cmd = ["git", "diff", "--name-only", args.start_rev, args.end_rev]
|
|
if args.verbose:
|
|
print(f"Running: {' '.join(cmd)}", file=sys.stderr)
|
|
|
|
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
if proc.returncode != 0:
|
|
print("Error: Failed to get changed files", file=sys.stderr)
|
|
sys.stderr.write(proc.stderr.decode("utf-8"))
|
|
return []
|
|
|
|
files = proc.stdout.decode("utf-8").strip().split("\n")
|
|
return [f for f in files if f]
|
|
|
|
def check_for_diff(self) -> Optional[str]:
|
|
"""Check if there are any uncommitted changes after running idt."""
|
|
cmd = ["git", "diff"]
|
|
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
diff = proc.stdout.decode("utf-8")
|
|
if diff:
|
|
return diff
|
|
return None
|
|
|
|
def run(self, args: IdsCheckArgs) -> int:
|
|
"""Main entry point for running ids check."""
|
|
# Resolve idt path: prefer command line arg, then env var
|
|
idt_path = args.idt_path or os.environ.get("IDT_PATH")
|
|
if not idt_path:
|
|
print(
|
|
"Error: idt path not specified. Use --idt-path argument or set IDT_PATH environment variable",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
|
|
if not os.path.exists(idt_path):
|
|
print(f"Error: idt tool not found at {idt_path}", file=sys.stderr)
|
|
return 1
|
|
|
|
# Resolve compile_commands path: prefer command line arg, then env var
|
|
compile_commands = args.compile_commands or os.environ.get(
|
|
"COMPILE_COMMANDS_PATH"
|
|
)
|
|
if not compile_commands:
|
|
print(
|
|
"Error: compile_commands.json path not specified. Use --compile-commands argument or set COMPILE_COMMANDS_PATH environment variable",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
|
|
if not os.path.exists(compile_commands):
|
|
print(
|
|
f"Error: compile_commands.json not found at {compile_commands}",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
|
|
# Get changed files
|
|
changed_files = self.get_changed_files(args)
|
|
if not changed_files:
|
|
if args.verbose:
|
|
print("No files changed, skipping ids check", file=sys.stderr)
|
|
return 0
|
|
|
|
# Process each category
|
|
any_processed = False
|
|
for category in self.FILE_CATEGORIES:
|
|
filtered_files = self.filter_files_for_category(changed_files, category)
|
|
if filtered_files:
|
|
any_processed = True
|
|
if not self.run_idt_on_files(
|
|
filtered_files, category, args, idt_path, compile_commands
|
|
):
|
|
return 1
|
|
|
|
if not any_processed:
|
|
if args.verbose:
|
|
print(
|
|
"No relevant header files changed, skipping ids check",
|
|
file=sys.stderr,
|
|
)
|
|
return 0
|
|
|
|
# Check for differences
|
|
diff = self.check_for_diff()
|
|
should_update_gh = args.token is not None and args.repo is not None
|
|
|
|
if diff:
|
|
if should_update_gh:
|
|
comment_text = self.pr_comment_text_for_diff(diff)
|
|
self.update_pr(comment_text, args, create_new=True)
|
|
else:
|
|
print(
|
|
"\nError: idt found missing LLVM_ABI annotations", file=sys.stderr
|
|
)
|
|
print(
|
|
"Apply the following diff to fix the LLVM_ABI annotations:\n",
|
|
file=sys.stderr,
|
|
)
|
|
print(diff)
|
|
return 1
|
|
else:
|
|
if should_update_gh:
|
|
comment_text = (
|
|
":white_check_mark: With the latest revision "
|
|
f"this PR passed the {self.friendly_name}."
|
|
)
|
|
self.update_pr(comment_text, args, create_new=False)
|
|
if args.verbose:
|
|
print("All files pass ids check", file=sys.stderr)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(
|
|
description="Check LLVM ABI annotations in header files"
|
|
)
|
|
parser.add_argument("--token", type=str, help="GitHub authentication token")
|
|
parser.add_argument(
|
|
"--repo",
|
|
type=str,
|
|
default=os.getenv("GITHUB_REPOSITORY", "llvm/llvm-project"),
|
|
help="The GitHub repository that we are working with in the form of <owner>/<repo> (e.g. llvm/llvm-project)",
|
|
)
|
|
parser.add_argument("--issue-number", type=int, help="GitHub issue/PR number")
|
|
parser.add_argument(
|
|
"--start-rev",
|
|
type=str,
|
|
required=True,
|
|
help="Compute changes from this revision",
|
|
)
|
|
parser.add_argument(
|
|
"--end-rev",
|
|
type=str,
|
|
required=True,
|
|
help="Compute changes to this revision",
|
|
)
|
|
parser.add_argument(
|
|
"--changed-files",
|
|
type=str,
|
|
help="Comma separated list of files that have been changed",
|
|
)
|
|
parser.add_argument(
|
|
"--idt-path",
|
|
type=str,
|
|
help="Path to the idt executable (can also be set via IDT_PATH environment variable)",
|
|
)
|
|
parser.add_argument(
|
|
"--compile-commands",
|
|
type=str,
|
|
help="Path to compile_commands.json (can also be set via COMPILE_COMMANDS_PATH environment variable)",
|
|
)
|
|
parser.add_argument(
|
|
"--verbose", action="store_true", default=True, help="Enable verbose output"
|
|
)
|
|
|
|
parsed_args = parser.parse_args()
|
|
|
|
# Parse changed files if provided
|
|
if parsed_args.changed_files:
|
|
parsed_args.changed_files = [
|
|
f.strip() for f in parsed_args.changed_files.split(",") if f.strip()
|
|
]
|
|
else:
|
|
parsed_args.changed_files = []
|
|
|
|
args = IdsCheckArgs(parsed_args)
|
|
checker = IdsChecker()
|
|
exit_code = checker.run(args)
|
|
|
|
if checker.comment:
|
|
with open("comments", "w") as f:
|
|
import json
|
|
|
|
json.dump([checker.comment], f)
|
|
|
|
sys.exit(exit_code)
|