[llvm] [GitHub][CI] Add clang-tidy premerge workflow (PR #154829)

Baranov Victor via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 27 14:35:07 PDT 2025


https://github.com/vbvictor updated https://github.com/llvm/llvm-project/pull/154829

>From 93be11e1540ac097c76659eca3b2e64f8267ba2b Mon Sep 17 00:00:00 2001
From: Victor Baranov <bar.victor.2002 at gmail.com>
Date: Thu, 21 Aug 2025 21:51:34 +0300
Subject: [PATCH 1/5] [GitHub][CI] Add clang-tidy premerge workflow

---
 .github/workflows/issue-write.yml          |   1 +
 .github/workflows/pr-code-lint.yml         | 126 +++++++
 llvm/utils/git/code-lint-helper.py         | 365 +++++++++++++++++++++
 llvm/utils/git/requirements_linting.txt    | 324 ++++++++++++++++++
 llvm/utils/git/requirements_linting.txt.in |   1 +
 5 files changed, 817 insertions(+)
 create mode 100644 .github/workflows/pr-code-lint.yml
 create mode 100755 llvm/utils/git/code-lint-helper.py
 create mode 100644 llvm/utils/git/requirements_linting.txt
 create mode 100644 llvm/utils/git/requirements_linting.txt.in

diff --git a/.github/workflows/issue-write.yml b/.github/workflows/issue-write.yml
index 3036582a64a58..db9389b6afe53 100644
--- a/.github/workflows/issue-write.yml
+++ b/.github/workflows/issue-write.yml
@@ -6,6 +6,7 @@ on:
       - "Check code formatting"
       - "Check for private emails used in PRs"
       - "PR Request Release Note"
+      - "Code lint"
     types:
       - completed
 
diff --git a/.github/workflows/pr-code-lint.yml b/.github/workflows/pr-code-lint.yml
new file mode 100644
index 0000000000000..89d5f5e499724
--- /dev/null
+++ b/.github/workflows/pr-code-lint.yml
@@ -0,0 +1,126 @@
+name: "Code lint"
+
+permissions:
+  contents: read
+
+on:
+  pull_request:
+    branches:
+      - main
+      - 'users/**'
+    paths:
+      - 'clang-tools-extra/clang-tidy/**'
+
+jobs:
+  code_linter:
+    if: github.repository_owner == 'llvm'
+    runs-on: ubuntu-24.04
+    defaults:
+      run:
+        shell: bash
+    container:
+      image: 'ghcr.io/llvm/ci-ubuntu-24.04:latest'
+    timeout-minutes: 60
+    concurrency:
+      group: ${{ github.workflow }}-${{ github.ref }}
+      cancel-in-progress: true
+    steps:
+      - name: Fetch LLVM sources
+        uses: actions/checkout at b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+        with:
+          fetch-depth: 2
+      
+      - name: Get changed files
+        id: changed-files
+        uses: step-security/changed-files at 3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
+        with:
+          separator: ","
+          skip_initial_fetch: true
+          base_sha: 'HEAD~1'
+          sha: 'HEAD'
+      
+      - name: Listed files
+        env:
+          CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
+        run: |
+          echo "Changed files:"
+          echo "$CHANGED_FILES"
+      
+      - name: Fetch code linting utils
+        uses: actions/checkout at 08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+        with:
+          repository: ${{ github.repository }}
+          ref: ${{ github.base_ref }}
+          sparse-checkout: |
+            llvm/utils/git/code-lint-helper.py
+            llvm/utils/git/requirements_linting.txt
+            clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py
+          sparse-checkout-cone-mode: false
+          path: code-lint-tools
+      
+      - uses: actions/setup-python at v5
+        id: setup_python
+        with:
+          python-version: '3.12'
+      
+      - name: Install dependencies
+        run: |
+          python3 -m venv .venv
+          source .venv/bin/activate
+          python3 -m pip install -r code-lint-tools/llvm/utils/git/requirements_linting.txt
+      
+      - name: Install clang-tidy
+        uses: aminya/setup-cpp at 17c11551771948abc5752bbf3183482567c7caf0 # v1.1.1
+        with:
+          clang-tidy: 20.1.8
+      
+      # FIXME: create special mapping for 'gen' targets, for now build predefined set
+      - name: Configure and CodeGen
+        run: |
+          git config --global --add safe.directory '*'
+
+          source <(git diff --name-only HEAD~1...HEAD | python3 .ci/compute_projects.py)
+
+          if [[ "${projects_to_build}" == "" ]]; then
+            echo "No projects to analyze"
+            exit 0
+          fi
+
+          cmake -G Ninja \
+                -B build \
+                -S llvm \
+                -DLLVM_ENABLE_ASSERTIONS=OFF \
+                -DLLVM_ENABLE_PROJECTS="${projects_to_build}" \
+                -DCMAKE_CXX_COMPILER=clang++ \
+                -DCMAKE_C_COMPILER=clang \
+                -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
+                -DLLVM_INCLUDE_TESTS=OFF \
+                -DCLANG_INCLUDE_TESTS=OFF \
+                -DCMAKE_BUILD_TYPE=Release
+          
+          ninja -C build \
+                clang-tablegen-targets \
+                genconfusable               # for "ConfusableIdentifierCheck.h"
+
+      - name: Run code linter
+        env:
+          GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
+          CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
+        run: |
+          source .venv/bin/activate
+          echo "[]" > comments &&
+          python3 ./code-lint-tools/llvm/utils/git/code-lint-helper.py \
+            --token ${{ secrets.GITHUB_TOKEN }} \
+            --issue-number $GITHUB_PR_NUMBER \
+            --start-rev HEAD~1 \
+            --end-rev HEAD \
+            --verbose \
+            --changed-files "$CHANGED_FILES"
+      
+      - name: Upload results
+        uses: actions/upload-artifact at 26f96dfa697d77e81fd5907df203aa23a56210a8 #v4.3.0
+        if: always()
+        with:
+          name: workflow-args
+          path: |
+            comments
diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py
new file mode 100755
index 0000000000000..fc6532f084e90
--- /dev/null
+++ b/llvm/utils/git/code-lint-helper.py
@@ -0,0 +1,365 @@
+#!/usr/bin/env python3
+#
+# ====- clang-tidy-helper, runs clang-tidy from the ci --*- python -*-------==#
+#
+# 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 PR's conform to
+the coding style of LLVM. The canonical source of this script is in the LLVM
+source tree under llvm/utils/git.
+
+You can learn more about the LLVM coding style on llvm.org:
+https://llvm.org/docs/CodingStandards.html
+"""
+
+
+class LintArgs:
+    start_rev: str = None
+    end_rev: str = None
+    repo: str = None
+    changed_files: List[str] = []
+    token: str = None
+    verbose: bool = True
+    issue_number: int = 0
+
+    def __init__(self, args: argparse.Namespace = None) -> None:
+        if not args is None:
+            self.start_rev = args.start_rev
+            self.end_rev = args.end_rev
+            self.repo = args.repo
+            self.token = args.token
+            self.changed_files = args.changed_files
+            self.issue_number = args.issue_number
+            self.verbose = args.verbose
+
+
+class LintHelper:
+    COMMENT_TAG = "<!--LLVM CODE LINT COMMENT: {linter}-->"
+    name: str
+    friendly_name: str
+    comment: dict = None
+
+    @property
+    def comment_tag(self) -> str:
+        return self.COMMENT_TAG.replace("linter", self.name)
+
+    @property
+    def instructions(self) -> str:
+        raise NotImplementedError()
+
+    def has_tool(self) -> bool:
+        raise NotImplementedError()
+
+    def lint_run(self, changed_files: List[str], args: LintArgs) -> Optional[str]:
+        raise NotImplementedError()
+
+    def pr_comment_text_for_output(self, warning: 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}
+``````````
+
+</details>
+
+<details>
+<summary>
+View the output from {self.name} here.
+</summary>
+
+``````````
+{warning}
+``````````
+
+</details>
+"""
+
+    def find_comment(self, pr: any) -> any:
+        for comment in pr.as_issue().get_comments():
+            if self.comment_tag in comment.body:
+                return comment
+        return None
+
+    def update_pr(self, comment_text: str, args: LintArgs, create_new: bool) -> None:
+        import github
+
+        repo = github.Github(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 = self.find_comment(pr)
+
+        if create_new or existing_comment:
+            self.comment = {"body": comment_text}
+        if existing_comment:
+            self.comment["id"] = existing_comment.id
+        return
+
+    def run(self, changed_files: List[str], args: LintArgs) -> bool:
+        changed_files = [arg for arg in changed_files if "third-party" not in arg]
+        diff = self.lint_run(changed_files, args)
+        should_update_gh = args.token is not None and args.repo is not None
+
+        if diff is None:
+            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)
+            return True
+        elif len(diff) > 0:
+            if should_update_gh:
+                comment_text = self.pr_comment_text_for_output(diff)
+                self.update_pr(comment_text, args, create_new=True)
+            else:
+                print(
+                    f"Warning: {self.friendly_name}, {self.name} detected "
+                    "some issues with your code formatting..."
+                )
+            return False
+        else:
+            # The linter failed but didn't output a result (e.g. some sort of
+            # infrastructure failure).
+            comment_text = (
+                f":warning: The {self.friendly_name} failed without printing "
+                "a diff. Check the logs for stderr output. :warning:"
+            )
+            self.update_pr(comment_text, args, create_new=False)
+            return False
+
+
+class ClangTidyDiffHelper(LintHelper):
+    name = "clang-tidy"
+    friendly_name = "C/C++ code linter"
+
+    def __init__(self, build_path: str, clang_tidy_binary: str):
+        self.build_path = build_path
+        self.clang_tidy_binary = clang_tidy_binary
+        self.cpp_files = []
+
+    def _clean_clang_tidy_output(self, output: str) -> str:
+        """
+        - Remove 'Running clang-tidy in X threads...' line
+        - Remove 'N warnings generated.' line
+        - Strip leading workspace path from file paths
+        """
+        if not output or output == "No relevant changes found.":
+            return None
+
+        lines = output.split("\n")
+        cleaned_lines = []
+
+        for line in lines:
+            if line.startswith("Running clang-tidy in") or line.endswith("generated."):
+                continue
+
+            # Remove everything up to rightmost "llvm-project/" for correct files names
+            idx = line.rfind("llvm-project/")
+            if idx != -1:
+                line = line[idx + len("llvm-project/") :]
+
+            cleaned_lines.append(line)
+
+        if cleaned_lines:
+            return "\n".join(cleaned_lines)
+        return None
+
+    @property
+    def instructions(self) -> str:
+        files_str = " ".join(self.cpp_files)
+        return f"""
+git diff -U0 origin/main..HEAD -- {files_str} |
+python3 clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py \\
+  -path build -p1 -quiet"""
+
+    # For add other paths/files to this function
+    def should_lint_file(self, filepath):
+        return filepath.startswith("clang-tools-extra/clang-tidy/")
+
+    def filter_changed_files(self, changed_files: List[str]) -> List[str]:
+        filtered_files = []
+        for filepath in changed_files:
+            _, ext = os.path.splitext(filepath)
+            if ext not in (".cpp", ".c", ".h", ".hpp", ".hxx", ".cxx"):
+                continue
+            if not self.should_lint_file(filepath):
+                continue
+            if os.path.exists(filepath):
+                filtered_files.append(filepath)
+
+        return filtered_files
+
+    def has_tool(self) -> bool:
+        cmd = [self.clang_tidy_binary, "--version"]
+        proc = None
+        try:
+            proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        except:
+            return False
+        return proc.returncode == 0
+
+    def lint_run(self, changed_files: List[str], args: LintArgs) -> Optional[str]:
+        cpp_files = self.filter_changed_files(changed_files)
+        if not cpp_files:
+            print("no c/c++ files found")
+            return None
+
+        self.cpp_files = cpp_files
+
+        git_diff_cmd = [
+            "git",
+            "diff",
+            "-U0",
+            f"{args.start_rev}..{args.end_rev}",
+            "--",
+        ] + cpp_files
+
+        diff_proc = subprocess.run(
+            git_diff_cmd,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+            text=True,
+            check=False,
+        )
+
+        if diff_proc.returncode != 0:
+            print(f"Git diff failed: {diff_proc.stderr}")
+            return None
+
+        diff_content = diff_proc.stdout
+        if not diff_content.strip():
+            print("No diff content found")
+            return None
+
+        tidy_diff_cmd = [
+            "code-lint-tools/clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py",
+            "-path",
+            self.build_path,
+            "-p1",
+            "-quiet",
+        ]
+
+        if args.verbose:
+            print(f"Running clang-tidy-diff: {' '.join(tidy_diff_cmd)}")
+
+        proc = subprocess.run(
+            tidy_diff_cmd,
+            input=diff_content,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+            text=True,
+            check=False,
+        )
+
+        return self._clean_clang_tidy_output(proc.stdout.strip())
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        "--token", type=str, required=True, help="GitHub authentication token"
+    )
+    parser.add_argument("--issue-number", type=int, required=True)
+    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(
+        "--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 has been changed",
+    )
+    parser.add_argument(
+        "--build-path",
+        type=str,
+        default="build",
+        help="Path to build directory with compile_commands.json",
+    )
+    parser.add_argument(
+        "--clang-tidy-binary",
+        type=str,
+        default="clang-tidy",
+        help="Path to clang-tidy binary",
+    )
+    parser.add_argument(
+        "--verbose", action="store_true", default=True, help="Verbose output"
+    )
+
+    parsed_args = parser.parse_args()
+    args = LintArgs(parsed_args)
+
+    changed_files = []
+    if args.changed_files:
+        changed_files = args.changed_files.split(",")
+
+    if args.verbose:
+        print(f"got changed files: {changed_files}")
+
+    all_linters = [
+        ClangTidyDiffHelper(
+            build_path=parsed_args.build_path,
+            clang_tidy_binary=parsed_args.clang_tidy_binary,
+        )
+    ]
+
+    failed_linters = []
+    comments = []
+
+    for linter in all_linters:
+        if not linter.has_tool():
+            print(f"Couldn't find {linter.friendly_name}: {linter.name}")
+            continue
+
+        if args.verbose:
+            print(f"running linter {linter.name}")
+
+        if not linter.run(changed_files, args):
+            if args.verbose:
+                print(f"linter {linter.name} failed")
+            failed_linters.append(linter.name)
+
+        if linter.comment:
+            if args.verbose:
+                print(f"linter {linter.name} has comment: {linter.comment}")
+            comments.append(linter.comment)
+
+    if len(comments) > 0:
+        with open("comments", "w") as f:
+            import json
+
+            json.dump(comments, f)
+
+    if len(failed_linters) > 0:
+        print(f"error: some linters failed: {' '.join(failed_linters)}")
+        # Do not fail job for as it may be unstable
+        # sys.exit(1)
diff --git a/llvm/utils/git/requirements_linting.txt b/llvm/utils/git/requirements_linting.txt
new file mode 100644
index 0000000000000..b985b80aa869e
--- /dev/null
+++ b/llvm/utils/git/requirements_linting.txt
@@ -0,0 +1,324 @@
+#
+# This file is autogenerated by pip-compile with Python 3.12
+# by the following command:
+#
+#    pip-compile --generate-hashes --output-file=requirements_linting.txt requirements_linting.txt.in
+#
+certifi==2025.8.3 \
+    --hash=sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407 \
+    --hash=sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5
+    # via requests
+cffi==1.17.1 \
+    --hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \
+    --hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \
+    --hash=sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1 \
+    --hash=sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15 \
+    --hash=sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36 \
+    --hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \
+    --hash=sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8 \
+    --hash=sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36 \
+    --hash=sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17 \
+    --hash=sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf \
+    --hash=sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc \
+    --hash=sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3 \
+    --hash=sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed \
+    --hash=sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702 \
+    --hash=sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1 \
+    --hash=sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8 \
+    --hash=sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903 \
+    --hash=sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6 \
+    --hash=sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d \
+    --hash=sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b \
+    --hash=sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e \
+    --hash=sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be \
+    --hash=sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c \
+    --hash=sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683 \
+    --hash=sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9 \
+    --hash=sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c \
+    --hash=sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8 \
+    --hash=sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1 \
+    --hash=sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4 \
+    --hash=sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655 \
+    --hash=sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67 \
+    --hash=sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595 \
+    --hash=sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0 \
+    --hash=sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65 \
+    --hash=sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41 \
+    --hash=sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6 \
+    --hash=sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401 \
+    --hash=sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6 \
+    --hash=sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3 \
+    --hash=sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16 \
+    --hash=sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93 \
+    --hash=sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e \
+    --hash=sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4 \
+    --hash=sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964 \
+    --hash=sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c \
+    --hash=sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576 \
+    --hash=sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0 \
+    --hash=sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3 \
+    --hash=sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662 \
+    --hash=sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3 \
+    --hash=sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff \
+    --hash=sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5 \
+    --hash=sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd \
+    --hash=sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f \
+    --hash=sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5 \
+    --hash=sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14 \
+    --hash=sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d \
+    --hash=sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9 \
+    --hash=sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7 \
+    --hash=sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382 \
+    --hash=sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a \
+    --hash=sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e \
+    --hash=sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a \
+    --hash=sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4 \
+    --hash=sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99 \
+    --hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \
+    --hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b
+    # via
+    #   cryptography
+    #   pynacl
+charset-normalizer==3.4.3 \
+    --hash=sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91 \
+    --hash=sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0 \
+    --hash=sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154 \
+    --hash=sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601 \
+    --hash=sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884 \
+    --hash=sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07 \
+    --hash=sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c \
+    --hash=sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64 \
+    --hash=sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe \
+    --hash=sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f \
+    --hash=sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432 \
+    --hash=sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc \
+    --hash=sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa \
+    --hash=sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9 \
+    --hash=sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae \
+    --hash=sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19 \
+    --hash=sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d \
+    --hash=sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e \
+    --hash=sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4 \
+    --hash=sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7 \
+    --hash=sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312 \
+    --hash=sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92 \
+    --hash=sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31 \
+    --hash=sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c \
+    --hash=sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f \
+    --hash=sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99 \
+    --hash=sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b \
+    --hash=sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15 \
+    --hash=sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392 \
+    --hash=sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f \
+    --hash=sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8 \
+    --hash=sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491 \
+    --hash=sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0 \
+    --hash=sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc \
+    --hash=sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0 \
+    --hash=sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f \
+    --hash=sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a \
+    --hash=sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40 \
+    --hash=sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927 \
+    --hash=sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849 \
+    --hash=sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce \
+    --hash=sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14 \
+    --hash=sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05 \
+    --hash=sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c \
+    --hash=sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c \
+    --hash=sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a \
+    --hash=sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc \
+    --hash=sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34 \
+    --hash=sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9 \
+    --hash=sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096 \
+    --hash=sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14 \
+    --hash=sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30 \
+    --hash=sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b \
+    --hash=sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b \
+    --hash=sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942 \
+    --hash=sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db \
+    --hash=sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5 \
+    --hash=sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b \
+    --hash=sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce \
+    --hash=sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669 \
+    --hash=sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0 \
+    --hash=sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018 \
+    --hash=sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93 \
+    --hash=sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe \
+    --hash=sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049 \
+    --hash=sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a \
+    --hash=sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef \
+    --hash=sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2 \
+    --hash=sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca \
+    --hash=sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16 \
+    --hash=sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f \
+    --hash=sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb \
+    --hash=sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1 \
+    --hash=sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557 \
+    --hash=sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37 \
+    --hash=sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7 \
+    --hash=sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72 \
+    --hash=sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c \
+    --hash=sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9
+    # via requests
+cryptography==45.0.6 \
+    --hash=sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5 \
+    --hash=sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74 \
+    --hash=sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394 \
+    --hash=sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301 \
+    --hash=sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08 \
+    --hash=sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3 \
+    --hash=sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b \
+    --hash=sha256:2384f2ab18d9be88a6e4f8972923405e2dbb8d3e16c6b43f15ca491d7831bd18 \
+    --hash=sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402 \
+    --hash=sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3 \
+    --hash=sha256:31a2b9a10530a1cb04ffd6aa1cd4d3be9ed49f7d77a4dafe198f3b382f41545c \
+    --hash=sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0 \
+    --hash=sha256:3b5bf5267e98661b9b888a9250d05b063220dfa917a8203744454573c7eb79db \
+    --hash=sha256:3de77e4df42ac8d4e4d6cdb342d989803ad37707cf8f3fbf7b088c9cbdd46427 \
+    --hash=sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f \
+    --hash=sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3 \
+    --hash=sha256:599c8d7df950aa68baa7e98f7b73f4f414c9f02d0e8104a30c0182a07732638b \
+    --hash=sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9 \
+    --hash=sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5 \
+    --hash=sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719 \
+    --hash=sha256:629127cfdcdc6806dfe234734d7cb8ac54edaf572148274fa377a7d3405b0043 \
+    --hash=sha256:705bb7c7ecc3d79a50f236adda12ca331c8e7ecfbea51edd931ce5a7a7c4f012 \
+    --hash=sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02 \
+    --hash=sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2 \
+    --hash=sha256:826b46dae41a1155a0c0e66fafba43d0ede1dc16570b95e40c4d83bfcf0a451d \
+    --hash=sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec \
+    --hash=sha256:cc4d66f5dc4dc37b89cfef1bd5044387f7a1f6f0abb490815628501909332d5d \
+    --hash=sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159 \
+    --hash=sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453 \
+    --hash=sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf \
+    --hash=sha256:e5b3dda1b00fb41da3af4c5ef3f922a200e33ee5ba0f0bc9ecf0b0c173958385 \
+    --hash=sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9 \
+    --hash=sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016 \
+    --hash=sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05 \
+    --hash=sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42 \
+    --hash=sha256:f68f833a9d445cc49f01097d95c83a850795921b3f7cc6488731e69bde3288da \
+    --hash=sha256:fc022c1fa5acff6def2fc6d7819bbbd31ccddfe67d075331a65d9cfb28a20983
+    # via pyjwt
+deprecated==1.2.18 \
+    --hash=sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d \
+    --hash=sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec
+    # via pygithub
+idna==3.10 \
+    --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \
+    --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3
+    # via requests
+pycparser==2.22 \
+    --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \
+    --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc
+    # via cffi
+pygithub==1.59.1 \
+    --hash=sha256:3d87a822e6c868142f0c2c4bf16cce4696b5a7a4d142a7bd160e1bdf75bc54a9 \
+    --hash=sha256:c44e3a121c15bf9d3a5cc98d94c9a047a5132a9b01d22264627f58ade9ddc217
+    # via -r requirements_linting.txt.in
+pyjwt[crypto]==2.10.1 \
+    --hash=sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953 \
+    --hash=sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb
+    # via pygithub
+pynacl==1.5.0 \
+    --hash=sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858 \
+    --hash=sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d \
+    --hash=sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93 \
+    --hash=sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1 \
+    --hash=sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92 \
+    --hash=sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff \
+    --hash=sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba \
+    --hash=sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394 \
+    --hash=sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b \
+    --hash=sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543
+    # via pygithub
+requests==2.32.5 \
+    --hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 \
+    --hash=sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf
+    # via pygithub
+urllib3==2.5.0 \
+    --hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \
+    --hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc
+    # via requests
+wrapt==1.17.3 \
+    --hash=sha256:02b551d101f31694fc785e58e0720ef7d9a10c4e62c1c9358ce6f63f23e30a56 \
+    --hash=sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828 \
+    --hash=sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f \
+    --hash=sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396 \
+    --hash=sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77 \
+    --hash=sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d \
+    --hash=sha256:0f5f51a6466667a5a356e6381d362d259125b57f059103dd9fdc8c0cf1d14139 \
+    --hash=sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7 \
+    --hash=sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb \
+    --hash=sha256:1f23fa283f51c890eda8e34e4937079114c74b4c81d2b2f1f1d94948f5cc3d7f \
+    --hash=sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f \
+    --hash=sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067 \
+    --hash=sha256:24c2ed34dc222ed754247a2702b1e1e89fdbaa4016f324b4b8f1a802d4ffe87f \
+    --hash=sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7 \
+    --hash=sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b \
+    --hash=sha256:30ce38e66630599e1193798285706903110d4f057aab3168a34b7fdc85569afc \
+    --hash=sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05 \
+    --hash=sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd \
+    --hash=sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7 \
+    --hash=sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9 \
+    --hash=sha256:3e62d15d3cfa26e3d0788094de7b64efa75f3a53875cdbccdf78547aed547a81 \
+    --hash=sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977 \
+    --hash=sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa \
+    --hash=sha256:46acc57b331e0b3bcb3e1ca3b421d65637915cfcd65eb783cb2f78a511193f9b \
+    --hash=sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe \
+    --hash=sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58 \
+    --hash=sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8 \
+    --hash=sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77 \
+    --hash=sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85 \
+    --hash=sha256:55cbbc356c2842f39bcc553cf695932e8b30e30e797f961860afb308e6b1bb7c \
+    --hash=sha256:59923aa12d0157f6b82d686c3fd8e1166fa8cdfb3e17b42ce3b6147ff81528df \
+    --hash=sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454 \
+    --hash=sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a \
+    --hash=sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e \
+    --hash=sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c \
+    --hash=sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6 \
+    --hash=sha256:656873859b3b50eeebe6db8b1455e99d90c26ab058db8e427046dbc35c3140a5 \
+    --hash=sha256:65d1d00fbfb3ea5f20add88bbc0f815150dbbde3b026e6c24759466c8b5a9ef9 \
+    --hash=sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd \
+    --hash=sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277 \
+    --hash=sha256:70d86fa5197b8947a2fa70260b48e400bf2ccacdcab97bb7de47e3d1e6312225 \
+    --hash=sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22 \
+    --hash=sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116 \
+    --hash=sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16 \
+    --hash=sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc \
+    --hash=sha256:758895b01d546812d1f42204bd443b8c433c44d090248bf22689df673ccafe00 \
+    --hash=sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2 \
+    --hash=sha256:7e18f01b0c3e4a07fe6dfdb00e29049ba17eadbc5e7609a2a3a4af83ab7d710a \
+    --hash=sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804 \
+    --hash=sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04 \
+    --hash=sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1 \
+    --hash=sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba \
+    --hash=sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390 \
+    --hash=sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0 \
+    --hash=sha256:a7c06742645f914f26c7f1fa47b8bc4c91d222f76ee20116c43d5ef0912bba2d \
+    --hash=sha256:a9a2203361a6e6404f80b99234fe7fb37d1fc73487b5a78dc1aa5b97201e0f22 \
+    --hash=sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0 \
+    --hash=sha256:ad85e269fe54d506b240d2d7b9f5f2057c2aa9a2ea5b32c66f8902f768117ed2 \
+    --hash=sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18 \
+    --hash=sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6 \
+    --hash=sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311 \
+    --hash=sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89 \
+    --hash=sha256:caea3e9c79d5f0d2c6d9ab96111601797ea5da8e6d0723f77eabb0d4068d2b2f \
+    --hash=sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39 \
+    --hash=sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4 \
+    --hash=sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5 \
+    --hash=sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa \
+    --hash=sha256:df7d30371a2accfe4013e90445f6388c570f103d61019b6b7c57e0265250072a \
+    --hash=sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050 \
+    --hash=sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6 \
+    --hash=sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235 \
+    --hash=sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056 \
+    --hash=sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2 \
+    --hash=sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418 \
+    --hash=sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c \
+    --hash=sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a \
+    --hash=sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6 \
+    --hash=sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0 \
+    --hash=sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775 \
+    --hash=sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10 \
+    --hash=sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c
+    # via deprecated
diff --git a/llvm/utils/git/requirements_linting.txt.in b/llvm/utils/git/requirements_linting.txt.in
new file mode 100644
index 0000000000000..33c997c022315
--- /dev/null
+++ b/llvm/utils/git/requirements_linting.txt.in
@@ -0,0 +1 @@
+PyGithub==1.59.1

>From 123d012ed4fadd81488da4c4a8d2f2ddc5d0a13d Mon Sep 17 00:00:00 2001
From: Victor Baranov <bar.victor.2002 at gmail.com>
Date: Fri, 22 Aug 2025 08:35:39 +0300
Subject: [PATCH 2/5] [GitHub][CI] make code-lint-helper.py a plain script

---
 llvm/utils/git/code-lint-helper.py | 404 ++++++++++++++---------------
 1 file changed, 192 insertions(+), 212 deletions(-)

diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py
index fc6532f084e90..de6d5cb7fc9e3 100755
--- a/llvm/utils/git/code-lint-helper.py
+++ b/llvm/utils/git/code-lint-helper.py
@@ -32,6 +32,8 @@ class LintArgs:
     token: str = None
     verbose: bool = True
     issue_number: int = 0
+    build_path: str = "build"
+    clang_tidy_binary: str = "clang-tidy"
 
     def __init__(self, args: argparse.Namespace = None) -> None:
         if not args is None:
@@ -42,235 +44,223 @@ def __init__(self, args: argparse.Namespace = None) -> None:
             self.changed_files = args.changed_files
             self.issue_number = args.issue_number
             self.verbose = args.verbose
+            self.build_path = args.build_path
+            self.clang_tidy_binary = args.clang_tidy_binary
 
 
-class LintHelper:
-    COMMENT_TAG = "<!--LLVM CODE LINT COMMENT: {linter}-->"
-    name: str
-    friendly_name: str
-    comment: dict = None
+COMMENT_TAG = "<!--LLVM CODE LINT COMMENT: clang-tidy-->"
 
-    @property
-    def comment_tag(self) -> str:
-        return self.COMMENT_TAG.replace("linter", self.name)
 
-    @property
-    def instructions(self) -> str:
-        raise NotImplementedError()
+def get_instructions(cpp_files: List[str]) -> str:
+    files_str = " ".join(cpp_files)
+    return f"""
+git diff -U0 origin/main..HEAD -- {files_str} |
+python3 clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py \\
+  -path build -p1 -quiet"""
+
+
+def clean_clang_tidy_output(output: str) -> Optional[str]:
+    """
+    - Remove 'Running clang-tidy in X threads...' line
+    - Remove 'N warnings generated.' line
+    - Strip leading workspace path from file paths
+    """
+    if not output or output == "No relevant changes found.":
+        return None
+
+    lines = output.split("\n")
+    cleaned_lines = []
+
+    for line in lines:
+        if line.startswith("Running clang-tidy in") or line.endswith("generated."):
+            continue
+
+        # Remove everything up to rightmost "llvm-project/" for correct files names
+        idx = line.rfind("llvm-project/")
+        if idx != -1:
+            line = line[idx + len("llvm-project/") :]
+
+        cleaned_lines.append(line)
+
+    if cleaned_lines:
+        return "\n".join(cleaned_lines)
+    return None
+
+
+def should_lint_file(filepath: str) -> bool:
+    # For add other paths/files to this function
+    return filepath.startswith("clang-tools-extra/clang-tidy/")
+
+
+def filter_changed_files(changed_files: List[str]) -> List[str]:
+    filtered_files = []
+    for filepath in changed_files:
+        _, ext = os.path.splitext(filepath)
+        if ext not in (".cpp", ".c", ".h", ".hpp", ".hxx", ".cxx"):
+            continue
+        if not should_lint_file(filepath):
+            continue
+        if os.path.exists(filepath):
+            filtered_files.append(filepath)
+
+    return filtered_files
 
-    def has_tool(self) -> bool:
-        raise NotImplementedError()
 
-    def lint_run(self, changed_files: List[str], args: LintArgs) -> Optional[str]:
-        raise NotImplementedError()
+def has_clang_tidy(clang_tidy_binary: str) -> bool:
+    cmd = [clang_tidy_binary, "--version"]
+    proc = None
+    try:
+        proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    except:
+        return False
+    return proc.returncode == 0
 
-    def pr_comment_text_for_output(self, warning: str) -> str:
-        return f"""
-:warning: {self.friendly_name} {self.name} found issues in your code. :warning:
+
+def create_comment_text(warning: str, cpp_files: List[str]) -> str:
+    instructions = get_instructions(cpp_files)
+    return f"""
+:warning: C/C++ code linter clang-tidy found issues in your code. :warning:
 
 <details>
 <summary>
 You can test this locally with the following command:
 </summary>
 
-``````````bash
-{self.instructions}
-``````````
+```bash
+{instructions}
+```
 
 </details>
 
 <details>
 <summary>
-View the output from {self.name} here.
+View the output from clang-tidy here.
 </summary>
 
-``````````
+```
 {warning}
-``````````
+```
 
 </details>
 """
 
-    def find_comment(self, pr: any) -> any:
-        for comment in pr.as_issue().get_comments():
-            if self.comment_tag in comment.body:
-                return comment
-        return None
 
-    def update_pr(self, comment_text: str, args: LintArgs, create_new: bool) -> None:
-        import github
-
-        repo = github.Github(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 = self.find_comment(pr)
-
-        if create_new or existing_comment:
-            self.comment = {"body": comment_text}
-        if existing_comment:
-            self.comment["id"] = existing_comment.id
-        return
-
-    def run(self, changed_files: List[str], args: LintArgs) -> bool:
-        changed_files = [arg for arg in changed_files if "third-party" not in arg]
-        diff = self.lint_run(changed_files, args)
-        should_update_gh = args.token is not None and args.repo is not None
-
-        if diff is None:
-            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)
-            return True
-        elif len(diff) > 0:
-            if should_update_gh:
-                comment_text = self.pr_comment_text_for_output(diff)
-                self.update_pr(comment_text, args, create_new=True)
-            else:
-                print(
-                    f"Warning: {self.friendly_name}, {self.name} detected "
-                    "some issues with your code formatting..."
-                )
-            return False
-        else:
-            # The linter failed but didn't output a result (e.g. some sort of
-            # infrastructure failure).
-            comment_text = (
-                f":warning: The {self.friendly_name} failed without printing "
-                "a diff. Check the logs for stderr output. :warning:"
-            )
-            self.update_pr(comment_text, args, create_new=False)
-            return False
+def find_comment(pr: any) -> any:
+    for comment in pr.as_issue().get_comments():
+        if COMMENT_TAG in comment.body:
+            return comment
+    return None
+
 
+def create_comment(
+    comment_text: str, args: LintArgs, create_new: bool
+) -> Optional[dict]:
+    import github
 
-class ClangTidyDiffHelper(LintHelper):
-    name = "clang-tidy"
-    friendly_name = "C/C++ code linter"
+    repo = github.Github(args.token).get_repo(args.repo)
+    pr = repo.get_issue(args.issue_number).as_pull_request()
 
-    def __init__(self, build_path: str, clang_tidy_binary: str):
-        self.build_path = build_path
-        self.clang_tidy_binary = clang_tidy_binary
-        self.cpp_files = []
+    comment_text = COMMENT_TAG + "\n\n" + comment_text
 
-    def _clean_clang_tidy_output(self, output: str) -> str:
-        """
-        - Remove 'Running clang-tidy in X threads...' line
-        - Remove 'N warnings generated.' line
-        - Strip leading workspace path from file paths
-        """
-        if not output or output == "No relevant changes found.":
-            return None
+    existing_comment = find_comment(pr)
 
-        lines = output.split("\n")
-        cleaned_lines = []
+    comment = None
+    if create_new or existing_comment:
+        comment = {"body": comment_text}
+    if existing_comment:
+        comment["id"] = existing_comment.id
+    return comment
 
-        for line in lines:
-            if line.startswith("Running clang-tidy in") or line.endswith("generated."):
-                continue
 
-            # Remove everything up to rightmost "llvm-project/" for correct files names
-            idx = line.rfind("llvm-project/")
-            if idx != -1:
-                line = line[idx + len("llvm-project/") :]
+def run_clang_tidy(changed_files: List[str], args: LintArgs) -> Optional[str]:
+    if not changed_files:
+        print("no c/c++ files found")
+        return None
 
-            cleaned_lines.append(line)
+    git_diff_cmd = [
+        "git",
+        "diff",
+        "-U0",
+        f"{args.start_rev}..{args.end_rev}",
+        "--",
+    ] + changed_files
+
+    diff_proc = subprocess.run(
+        git_diff_cmd,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        text=True,
+        check=False,
+    )
 
-        if cleaned_lines:
-            return "\n".join(cleaned_lines)
+    if diff_proc.returncode != 0:
+        print(f"Git diff failed: {diff_proc.stderr}")
         return None
 
-    @property
-    def instructions(self) -> str:
-        files_str = " ".join(self.cpp_files)
-        return f"""
-git diff -U0 origin/main..HEAD -- {files_str} |
-python3 clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py \\
-  -path build -p1 -quiet"""
+    diff_content = diff_proc.stdout
+    if not diff_content.strip():
+        print("No diff content found")
+        return None
 
-    # For add other paths/files to this function
-    def should_lint_file(self, filepath):
-        return filepath.startswith("clang-tools-extra/clang-tidy/")
-
-    def filter_changed_files(self, changed_files: List[str]) -> List[str]:
-        filtered_files = []
-        for filepath in changed_files:
-            _, ext = os.path.splitext(filepath)
-            if ext not in (".cpp", ".c", ".h", ".hpp", ".hxx", ".cxx"):
-                continue
-            if not self.should_lint_file(filepath):
-                continue
-            if os.path.exists(filepath):
-                filtered_files.append(filepath)
-
-        return filtered_files
-
-    def has_tool(self) -> bool:
-        cmd = [self.clang_tidy_binary, "--version"]
-        proc = None
-        try:
-            proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-        except:
-            return False
-        return proc.returncode == 0
-
-    def lint_run(self, changed_files: List[str], args: LintArgs) -> Optional[str]:
-        cpp_files = self.filter_changed_files(changed_files)
-        if not cpp_files:
-            print("no c/c++ files found")
-            return None
-
-        self.cpp_files = cpp_files
-
-        git_diff_cmd = [
-            "git",
-            "diff",
-            "-U0",
-            f"{args.start_rev}..{args.end_rev}",
-            "--",
-        ] + cpp_files
-
-        diff_proc = subprocess.run(
-            git_diff_cmd,
-            stdout=subprocess.PIPE,
-            stderr=subprocess.PIPE,
-            text=True,
-            check=False,
-        )
+    tidy_diff_cmd = [
+        "code-lint-tools/clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py",
+        "-path",
+        args.build_path,
+        "-p1",
+        "-quiet",
+    ]
 
-        if diff_proc.returncode != 0:
-            print(f"Git diff failed: {diff_proc.stderr}")
-            return None
+    if args.verbose:
+        print(f"Running clang-tidy-diff: {' '.join(tidy_diff_cmd)}")
+
+    proc = subprocess.run(
+        tidy_diff_cmd,
+        input=diff_content,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        text=True,
+        check=False,
+    )
 
-        diff_content = diff_proc.stdout
-        if not diff_content.strip():
-            print("No diff content found")
-            return None
+    return clean_clang_tidy_output(proc.stdout.strip())
 
-        tidy_diff_cmd = [
-            "code-lint-tools/clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py",
-            "-path",
-            self.build_path,
-            "-p1",
-            "-quiet",
-        ]
 
-        if args.verbose:
-            print(f"Running clang-tidy-diff: {' '.join(tidy_diff_cmd)}")
-
-        proc = subprocess.run(
-            tidy_diff_cmd,
-            input=diff_content,
-            stdout=subprocess.PIPE,
-            stderr=subprocess.PIPE,
-            text=True,
-            check=False,
-        )
+def run_linter(changed_files: List[str], args: LintArgs) -> tuple[bool, Optional[dict]]:
+    changed_files = [arg for arg in changed_files if "third-party" not in arg]
 
-        return self._clean_clang_tidy_output(proc.stdout.strip())
+    cpp_files = filter_changed_files(changed_files)
+
+    tidy_result = run_clang_tidy(cpp_files, args)
+    should_update_gh = args.token is not None and args.repo is not None
+
+    comment = None
+    if tidy_result is None:
+        if should_update_gh:
+            comment_text = (
+                ":white_check_mark: With the latest revision "
+                "this PR passed the C/C++ code linter."
+            )
+            comment = create_comment(comment_text, args, create_new=False)
+        return True, comment
+    elif len(tidy_result) > 0:
+        if should_update_gh:
+            comment_text = create_comment_text(tidy_result, cpp_files)
+            comment = create_comment(comment_text, args, create_new=True)
+        else:
+            print(
+                "Warning: C/C++ code linter, clang-tidy detected "
+                "some issues with your code..."
+            )
+        return False, comment
+    else:
+        # The linter failed but didn't output a result (e.g. some sort of
+        # infrastructure failure).
+        comment_text = (
+            ":warning: The C/C++ code linter failed without printing "
+            "an output. Check the logs for output. :warning:"
+        )
+        comment = create_comment(comment_text, args, create_new=False)
+        return False, comment
 
 
 if __name__ == "__main__":
@@ -325,41 +315,31 @@ def lint_run(self, changed_files: List[str], args: LintArgs) -> Optional[str]:
     if args.verbose:
         print(f"got changed files: {changed_files}")
 
-    all_linters = [
-        ClangTidyDiffHelper(
-            build_path=parsed_args.build_path,
-            clang_tidy_binary=parsed_args.clang_tidy_binary,
-        )
-    ]
+    # Check for clang-tidy tool
+    if not has_clang_tidy(args.clang_tidy_binary):
+        print("Couldn't find C/C++ code linter: clang-tidy")
+        sys.exit(1)
 
-    failed_linters = []
-    comments = []
+    if args.verbose:
+        print("running linter clang-tidy")
 
-    for linter in all_linters:
-        if not linter.has_tool():
-            print(f"Couldn't find {linter.friendly_name}: {linter.name}")
-            continue
+    success, comment = run_linter(changed_files, args)
 
+    if not success:
         if args.verbose:
-            print(f"running linter {linter.name}")
-
-        if not linter.run(changed_files, args):
-            if args.verbose:
-                print(f"linter {linter.name} failed")
-            failed_linters.append(linter.name)
+            print("linter clang-tidy failed")
 
-        if linter.comment:
-            if args.verbose:
-                print(f"linter {linter.name} has comment: {linter.comment}")
-            comments.append(linter.comment)
+    # Write comments file if we have a comment
+    if comment:
+        if args.verbose:
+            print(f"linter clang-tidy has comment: {comment}")
 
-    if len(comments) > 0:
         with open("comments", "w") as f:
             import json
 
-            json.dump(comments, f)
+            json.dump([comment], f)
 
-    if len(failed_linters) > 0:
-        print(f"error: some linters failed: {' '.join(failed_linters)}")
+    if not success:
+        print("error: some linters failed: clang-tidy")
         # Do not fail job for as it may be unstable
         # sys.exit(1)

>From fb310f01a32cd251d3b625daf5fde1390106b741 Mon Sep 17 00:00:00 2001
From: Victor Baranov <bar.victor.2002 at gmail.com>
Date: Sun, 24 Aug 2025 13:18:52 +0300
Subject: [PATCH 3/5] [GitHub][CI] Fix PR comment from boomanaiden154

---
 .github/workflows/pr-code-lint.yml | 14 +++++-------
 llvm/utils/git/code-lint-helper.py | 34 ++++++++----------------------
 2 files changed, 14 insertions(+), 34 deletions(-)

diff --git a/.github/workflows/pr-code-lint.yml b/.github/workflows/pr-code-lint.yml
index 89d5f5e499724..c04adc01cbd1a 100644
--- a/.github/workflows/pr-code-lint.yml
+++ b/.github/workflows/pr-code-lint.yml
@@ -58,16 +58,13 @@ jobs:
           sparse-checkout-cone-mode: false
           path: code-lint-tools
       
-      - uses: actions/setup-python at v5
-        id: setup_python
+      - name: Install python
+        uses: actions/setup-python at a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
         with:
           python-version: '3.12'
-      
-      - name: Install dependencies
-        run: |
-          python3 -m venv .venv
-          source .venv/bin/activate
-          python3 -m pip install -r code-lint-tools/llvm/utils/git/requirements_linting.txt
+
+      - name: Install python dependencies
+        run: python3 -m pip install -r code-lint-tools/llvm/utils/git/requirements_linting.txt
       
       - name: Install clang-tidy
         uses: aminya/setup-cpp at 17c11551771948abc5752bbf3183482567c7caf0 # v1.1.1
@@ -107,7 +104,6 @@ jobs:
           GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
           CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
         run: |
-          source .venv/bin/activate
           echo "[]" > comments &&
           python3 ./code-lint-tools/llvm/utils/git/code-lint-helper.py \
             --token ${{ secrets.GITHUB_TOKEN }} \
diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py
index de6d5cb7fc9e3..871a5eb9b4779 100755
--- a/llvm/utils/git/code-lint-helper.py
+++ b/llvm/utils/git/code-lint-helper.py
@@ -7,14 +7,8 @@
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 #
 # ==-------------------------------------------------------------------------==#
+"""A helper script to run clang-tidy linter in GitHub actions
 
-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 PR's conform to
 the coding style of LLVM. The canonical source of this script is in the LLVM
 source tree under llvm/utils/git.
@@ -23,6 +17,11 @@
 https://llvm.org/docs/CodingStandards.html
 """
 
+import argparse
+import os
+import subprocess
+from typing import List, Optional
+
 
 class LintArgs:
     start_rev: str = None
@@ -54,7 +53,7 @@ def __init__(self, args: argparse.Namespace = None) -> None:
 def get_instructions(cpp_files: List[str]) -> str:
     files_str = " ".join(cpp_files)
     return f"""
-git diff -U0 origin/main..HEAD -- {files_str} |
+git diff -U0 origin/main...HEAD -- {files_str} |
 python3 clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py \\
   -path build -p1 -quiet"""
 
@@ -87,8 +86,8 @@ def clean_clang_tidy_output(output: str) -> Optional[str]:
     return None
 
 
+# TODO: Add more rules when enabling other projects to use clang-tidy in CI.
 def should_lint_file(filepath: str) -> bool:
-    # For add other paths/files to this function
     return filepath.startswith("clang-tools-extra/clang-tidy/")
 
 
@@ -106,16 +105,6 @@ def filter_changed_files(changed_files: List[str]) -> List[str]:
     return filtered_files
 
 
-def has_clang_tidy(clang_tidy_binary: str) -> bool:
-    cmd = [clang_tidy_binary, "--version"]
-    proc = None
-    try:
-        proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-    except:
-        return False
-    return proc.returncode == 0
-
-
 def create_comment_text(warning: str, cpp_files: List[str]) -> str:
     instructions = get_instructions(cpp_files)
     return f"""
@@ -181,7 +170,7 @@ def run_clang_tidy(changed_files: List[str], args: LintArgs) -> Optional[str]:
         "git",
         "diff",
         "-U0",
-        f"{args.start_rev}..{args.end_rev}",
+        f"{args.start_rev}...{args.end_rev}",
         "--",
     ] + changed_files
 
@@ -315,11 +304,6 @@ def run_linter(changed_files: List[str], args: LintArgs) -> tuple[bool, Optional
     if args.verbose:
         print(f"got changed files: {changed_files}")
 
-    # Check for clang-tidy tool
-    if not has_clang_tidy(args.clang_tidy_binary):
-        print("Couldn't find C/C++ code linter: clang-tidy")
-        sys.exit(1)
-
     if args.verbose:
         print("running linter clang-tidy")
 

>From 7adc93bff569793083287fac075888cca502622f Mon Sep 17 00:00:00 2001
From: Victor Baranov <bar.victor.2002 at gmail.com>
Date: Mon, 25 Aug 2025 22:36:38 +0300
Subject: [PATCH 4/5] [GitHub][CI] Add pip cache and enable hard failure in
 lint-helper.py

---
 .github/workflows/pr-code-lint.yml | 24 +++++++++++++++---------
 llvm/utils/git/code-lint-helper.py |  4 ++--
 2 files changed, 17 insertions(+), 11 deletions(-)

diff --git a/.github/workflows/pr-code-lint.yml b/.github/workflows/pr-code-lint.yml
index c04adc01cbd1a..4ac475942db23 100644
--- a/.github/workflows/pr-code-lint.yml
+++ b/.github/workflows/pr-code-lint.yml
@@ -57,21 +57,27 @@ jobs:
             clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py
           sparse-checkout-cone-mode: false
           path: code-lint-tools
-      
-      - name: Install python
-        uses: actions/setup-python at a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
-        with:
-          python-version: '3.12'
 
-      - name: Install python dependencies
-        run: python3 -m pip install -r code-lint-tools/llvm/utils/git/requirements_linting.txt
-      
       - name: Install clang-tidy
         uses: aminya/setup-cpp at 17c11551771948abc5752bbf3183482567c7caf0 # v1.1.1
         with:
           clang-tidy: 20.1.8
+
+      - name: Clear Python cache
+        run: sudo rm -rf /__t/Python
+      
+      - name: Setup Python env
+        uses: actions/setup-python at 42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
+        with:
+          python-version: '3.12'
+          cache: 'pip'
+          cache-dependency-path: 'code-lint-tools/llvm/utils/git/requirements_linting.txt'
+
+      - name: Install Python dependencies
+        run: pip install -r code-lint-tools/llvm/utils/git/requirements_linting.txt
       
-      # FIXME: create special mapping for 'gen' targets, for now build predefined set
+      # TODO: create special mapping for 'codegen' targets, for now build predefined set
+      # TODO: add entrypoint in 'compute_projects.py' that only adds a project and its direct dependencies
       - name: Configure and CodeGen
         run: |
           git config --global --add safe.directory '*'
diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py
index 871a5eb9b4779..dd1f2ec37bf98 100755
--- a/llvm/utils/git/code-lint-helper.py
+++ b/llvm/utils/git/code-lint-helper.py
@@ -20,6 +20,7 @@
 import argparse
 import os
 import subprocess
+import sys
 from typing import List, Optional
 
 
@@ -325,5 +326,4 @@ def run_linter(changed_files: List[str], args: LintArgs) -> tuple[bool, Optional
 
     if not success:
         print("error: some linters failed: clang-tidy")
-        # Do not fail job for as it may be unstable
-        # sys.exit(1)
+        sys.exit(1)

>From 8ba55c84f2065a2e9c5cb966ef8be5200d4196cf Mon Sep 17 00:00:00 2001
From: Victor Baranov <bar.victor.2002 at gmail.com>
Date: Thu, 28 Aug 2025 00:28:37 +0300
Subject: [PATCH 5/5] [GitHub][CI] Remove caching and default bash shell

---
 .github/workflows/pr-code-lint.yml | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/pr-code-lint.yml b/.github/workflows/pr-code-lint.yml
index 4ac475942db23..adb6c6e8f4c76 100644
--- a/.github/workflows/pr-code-lint.yml
+++ b/.github/workflows/pr-code-lint.yml
@@ -62,19 +62,14 @@ jobs:
         uses: aminya/setup-cpp at 17c11551771948abc5752bbf3183482567c7caf0 # v1.1.1
         with:
           clang-tidy: 20.1.8
-
-      - name: Clear Python cache
-        run: sudo rm -rf /__t/Python
       
       - name: Setup Python env
         uses: actions/setup-python at 42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
         with:
           python-version: '3.12'
-          cache: 'pip'
-          cache-dependency-path: 'code-lint-tools/llvm/utils/git/requirements_linting.txt'
 
       - name: Install Python dependencies
-        run: pip install -r code-lint-tools/llvm/utils/git/requirements_linting.txt
+        run: python3 -m pip install -r code-lint-tools/llvm/utils/git/requirements_linting.txt
       
       # TODO: create special mapping for 'codegen' targets, for now build predefined set
       # TODO: add entrypoint in 'compute_projects.py' that only adds a project and its direct dependencies
@@ -82,7 +77,7 @@ jobs:
         run: |
           git config --global --add safe.directory '*'
 
-          source <(git diff --name-only HEAD~1...HEAD | python3 .ci/compute_projects.py)
+          . <(git diff --name-only HEAD~1...HEAD | python3 .ci/compute_projects.py)
 
           if [[ "${projects_to_build}" == "" ]]; then
             echo "No projects to analyze"



More information about the llvm-commits mailing list