https://github.com/zeyi2 updated https://github.com/llvm/llvm-project/pull/168827
>From 9552857532ca486931f0bcb247d1cd5df6a70c4d Mon Sep 17 00:00:00 2001 From: mtx <[email protected]> Date: Thu, 20 Nov 2025 13:03:28 +0800 Subject: [PATCH 01/10] [Github][CI] Add `doc8` for clang-tidy documentation formatting --- .../github-action-ci-tooling/Dockerfile | 4 + .github/workflows/pr-code-lint.yml | 25 ++- llvm/utils/git/code-lint-helper.py | 201 ++++++++++++++++-- 3 files changed, 203 insertions(+), 27 deletions(-) diff --git a/.github/workflows/containers/github-action-ci-tooling/Dockerfile b/.github/workflows/containers/github-action-ci-tooling/Dockerfile index b78c99efb9be3..8d02baa05f489 100644 --- a/.github/workflows/containers/github-action-ci-tooling/Dockerfile +++ b/.github/workflows/containers/github-action-ci-tooling/Dockerfile @@ -94,6 +94,10 @@ COPY --from=llvm-downloader /llvm-extract/LLVM-${LLVM_VERSION}-Linux-X64/bin/cla COPY clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py ${LLVM_SYSROOT}/bin/clang-tidy-diff.py # Install dependencies for 'pr-code-lint.yml' job +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y python3-doc8 && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* COPY llvm/utils/git/requirements_linting.txt requirements_linting.txt RUN pip install -r requirements_linting.txt --break-system-packages && \ rm requirements_linting.txt diff --git a/.github/workflows/pr-code-lint.yml b/.github/workflows/pr-code-lint.yml index 5444a29c22205..60c1900000e5e 100644 --- a/.github/workflows/pr-code-lint.yml +++ b/.github/workflows/pr-code-lint.yml @@ -30,7 +30,7 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 2 - + - name: Get changed files id: changed-files uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0 @@ -39,14 +39,14 @@ jobs: 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" - + # 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 @@ -71,25 +71,38 @@ jobs: -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 + - name: Run clang-tidy linter env: GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} run: | echo "[]" > comments && python3 llvm/utils/git/code-lint-helper.py \ + --linter clang-tidy \ --token ${{ secrets.GITHUB_TOKEN }} \ --issue-number $GITHUB_PR_NUMBER \ --start-rev HEAD~1 \ --end-rev HEAD \ --verbose \ --changed-files "$CHANGED_FILES" - + + - name: Run doc8 linter + env: + GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + python3 llvm/utils/git/code-lint-helper.py \ + --linter doc8 \ + --token ${{ secrets.GITHUB_TOKEN }} \ + --issue-number $GITHUB_PR_NUMBER \ + --start-rev HEAD~1 \ + --end-rev HEAD \ + --verbose + - name: Upload results uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 if: always() diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py index 1232f3ab0d370..fc2068b438209 100755 --- a/llvm/utils/git/code-lint-helper.py +++ b/llvm/utils/git/code-lint-helper.py @@ -34,6 +34,8 @@ class LintArgs: issue_number: int = 0 build_path: str = "build" clang_tidy_binary: str = "clang-tidy" + doc8_binary: str = "doc8" + linter: str = None def __init__(self, args: argparse.Namespace = None) -> None: if not args is None: @@ -46,9 +48,12 @@ def __init__(self, args: argparse.Namespace = None) -> None: self.verbose = args.verbose self.build_path = args.build_path self.clang_tidy_binary = args.clang_tidy_binary + self.doc8_binary = args.doc8_binary + self.linter = args.linter -COMMENT_TAG = "<!--LLVM CODE LINT COMMENT: clang-tidy-->" +COMMENT_TAG_CLANG_TIDY = "<!--LLVM CODE LINT COMMENT: clang-tidy-->" +COMMENT_TAG_DOC8 = "<!--LLVM CODE LINT COMMENT: doc8-->" def get_instructions(cpp_files: List[str]) -> str: @@ -135,13 +140,22 @@ def create_comment_text(warning: str, cpp_files: List[str]) -> str: """ -def find_comment(pr: any) -> any: +def find_comment(pr: any, args: LintArgs) -> any: + comment_tag = get_comment_tag(args.linter) for comment in pr.as_issue().get_comments(): - if COMMENT_TAG in comment.body: + if comment_tag in comment.body: return comment return None +def get_comment_tag(linter: str) -> str: + if linter == "clang-tidy": + return COMMENT_TAG_CLANG_TIDY + elif linter == "doc8": + return COMMENT_TAG_DOC8 + raise ValueError(f"Unknown linter: {linter}") + + def create_comment( comment_text: str, args: LintArgs, create_new: bool ) -> Optional[dict]: @@ -150,9 +164,10 @@ def create_comment( repo = github.Github(args.token).get_repo(args.repo) pr = repo.get_issue(args.issue_number).as_pull_request() - comment_text = COMMENT_TAG + "\n\n" + comment_text + comment_tag = get_comment_tag(args.linter) + comment_text = comment_tag + "\n\n" + comment_text - existing_comment = find_comment(pr) + existing_comment = find_comment(pr, args) comment = None if create_new or existing_comment: @@ -215,7 +230,126 @@ def run_clang_tidy(changed_files: List[str], args: LintArgs) -> Optional[str]: return clean_clang_tidy_output(proc.stdout.strip()) -def run_linter(changed_files: List[str], args: LintArgs) -> tuple[bool, Optional[dict]]: + +def clean_doc8_output(output: str) -> Optional[str]: + if not output: + return None + + lines = output.split("\n") + cleaned_lines = [] + in_summary = False + + for line in lines: + if line.startswith("Scanning...") or line.startswith("Validating..."): + continue + if line.startswith("========"): + in_summary = True + continue + if in_summary: + continue + if line.strip(): + cleaned_lines.append(line) + + if cleaned_lines: + return "\n".join(cleaned_lines) + return None + + +def get_doc8_instructions() -> str: + # TODO: use git diff + return "doc8 ./clang-tools-extra/docs/clang-tidy/checks/" + + +def create_doc8_comment_text(doc8_output: str) -> str: + instructions = get_doc8_instructions() + return f""" +:warning: Documentation linter doc8 found issues in your code. :warning: + +<details> +<summary> +You can test this locally with the following command: +</summary> + +```bash +{instructions} +``` + +</details> + +<details> +<summary> +View the output from doc8 here. +</summary> + +``` +{doc8_output} +``` + +</details> +""" + + +def run_doc8(args: LintArgs) -> tuple[int, Optional[str]]: + doc8_cmd = [args.doc8_binary, "./clang-tools-extra/docs/clang-tidy/checks/"] + + if args.verbose: + print(f"Running doc8: {' '.join(doc8_cmd)}") + + proc = subprocess.run( + doc8_cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=False, + ) + + cleaned_output = clean_doc8_output(proc.stdout.strip()) + if proc.returncode != 0 and cleaned_output is None: + # Infrastructure failure + return proc.returncode, proc.stderr.strip() + + return proc.returncode, cleaned_output + + +def run_doc8_linter(args: LintArgs) -> tuple[bool, Optional[dict]]: + returncode, result = run_doc8(args) + should_update_gh = args.token is not None and args.repo is not None + comment = None + + if returncode == 0: + if should_update_gh: + comment_text = ( + ":white_check_mark: With the latest revision " + "this PR passed the documentation linter." + ) + comment = create_comment(comment_text, args, create_new=False) + return True, comment + else: + if should_update_gh: + if result: + comment_text = create_doc8_comment_text(result) + comment = create_comment(comment_text, args, create_new=True) + else: + comment_text = ( + ":warning: The documentation linter failed without printing " + "an output. Check the logs for output. :warning:" + ) + comment = create_comment(comment_text, args, create_new=False) + else: + if result: + print( + "Warning: Documentation linter, doc8 detected " + "some issues with your code..." + ) + print(result) + else: + print("Warning: Documentation linter, doc8 failed to run.") + return False, comment + + +def run_clang_tidy_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] cpp_files = filter_changed_files(changed_files) @@ -255,6 +389,13 @@ def run_linter(changed_files: List[str], args: LintArgs) -> tuple[bool, Optional if __name__ == "__main__": parser = argparse.ArgumentParser() + parser.add_argument( + "--linter", + type=str, + choices=["clang-tidy", "doc8"], + required=True, + help="The linter to run.", + ) parser.add_argument( "--token", type=str, required=True, help="GitHub authentication token" ) @@ -291,6 +432,12 @@ def run_linter(changed_files: List[str], args: LintArgs) -> tuple[bool, Optional default="clang-tidy", help="Path to clang-tidy binary", ) + parser.add_argument( + "--doc8-binary", + type=str, + default="doc8", + help="Path to doc8 binary", + ) parser.add_argument( "--verbose", action="store_true", default=True, help="Verbose output" ) @@ -298,32 +445,44 @@ def run_linter(changed_files: List[str], args: LintArgs) -> tuple[bool, Optional 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}") - if args.verbose: - print("running linter clang-tidy") + print(f"running linter {args.linter}") - success, comment = run_linter(changed_files, args) + success, comment = False, None + if args.linter == "clang-tidy": + changed_files = [] + if args.changed_files: + changed_files = args.changed_files.split(",") + if args.verbose: + print(f"got changed files: {changed_files}") + success, comment = run_clang_tidy_linter(changed_files, args) + elif args.linter == "doc8": + success, comment = run_doc8_linter(args) if not success: if args.verbose: - print("linter clang-tidy failed") + print(f"linter {args.linter} failed") # Write comments file if we have a comment if comment: + import json if args.verbose: - print(f"linter clang-tidy has comment: {comment}") + print(f"linter {args.linter} has comment: {comment}") - with open("comments", "w") as f: - import json + existing_comments = [] + if os.path.exists("comments"): + with open("comments", "r") as f: + try: + existing_comments = json.load(f) + except json.JSONDecodeError: + # File might be empty or invalid, start fresh + pass - json.dump([comment], f) + existing_comments.append(comment) + + with open("comments", "w") as f: + json.dump(existing_comments, f) if not success: - print("error: some linters failed: clang-tidy") + print(f"error: linter {args.linter} failed") sys.exit(1) >From 8fc630014edd5046c39d9da8831cad5a4f0d23b2 Mon Sep 17 00:00:00 2001 From: mtx <[email protected]> Date: Thu, 20 Nov 2025 13:43:18 +0800 Subject: [PATCH 02/10] Try to make CI work --- .github/workflows/pr-code-lint.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr-code-lint.yml b/.github/workflows/pr-code-lint.yml index 60c1900000e5e..33b84117fea8b 100644 --- a/.github/workflows/pr-code-lint.yml +++ b/.github/workflows/pr-code-lint.yml @@ -76,6 +76,9 @@ jobs: clang-tablegen-targets \ genconfusable # for "ConfusableIdentifierCheck.h" + - name: Install linter dependencies + run: pip install doc8 --break-system-packages + - name: Run clang-tidy linter env: GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} >From 3a5c436a2efa0bb2bc8128ccd3e11b53b30cb611 Mon Sep 17 00:00:00 2001 From: mtx <[email protected]> Date: Thu, 20 Nov 2025 13:57:26 +0800 Subject: [PATCH 03/10] Dirty fix again --- .github/workflows/pr-code-lint.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-code-lint.yml b/.github/workflows/pr-code-lint.yml index 33b84117fea8b..337a033f4848c 100644 --- a/.github/workflows/pr-code-lint.yml +++ b/.github/workflows/pr-code-lint.yml @@ -77,7 +77,9 @@ jobs: genconfusable # for "ConfusableIdentifierCheck.h" - name: Install linter dependencies - run: pip install doc8 --break-system-packages + run: | + pip install doc8 --break-system-packages + echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Run clang-tidy linter env: >From 27c30abf7464821af5f94a0b45c9f8871e7d2648 Mon Sep 17 00:00:00 2001 From: mtx <[email protected]> Date: Thu, 20 Nov 2025 14:08:36 +0800 Subject: [PATCH 04/10] Fix comment issue --- llvm/utils/git/code-lint-helper.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py index fc2068b438209..17cdd02cfceb6 100755 --- a/llvm/utils/git/code-lint-helper.py +++ b/llvm/utils/git/code-lint-helper.py @@ -141,9 +141,14 @@ def create_comment_text(warning: str, cpp_files: List[str]) -> str: def find_comment(pr: any, args: LintArgs) -> any: - comment_tag = get_comment_tag(args.linter) + linter_tag = get_comment_tag(args.linter) + other_linter = "doc8" if args.linter == "clang-tidy" else "clang-tidy" + other_tag = get_comment_tag(other_linter) + for comment in pr.as_issue().get_comments(): - if comment_tag in comment.body: + body = comment.body + if linter_tag in body and other_tag not in body: + # Found a comment that is exclusively for this linter. return comment return None @@ -230,7 +235,6 @@ def run_clang_tidy(changed_files: List[str], args: LintArgs) -> Optional[str]: return clean_clang_tidy_output(proc.stdout.strip()) - def clean_doc8_output(output: str) -> Optional[str]: if not output: return None >From 75acf3ba9a2174ff92062cb7d74016a9c13ac296 Mon Sep 17 00:00:00 2001 From: mtx <[email protected]> Date: Fri, 21 Nov 2025 14:27:57 +0800 Subject: [PATCH 05/10] Update scripts --- llvm/utils/git/code-lint-helper.py | 78 ++++++++++++++++-------------- 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py index 17cdd02cfceb6..7172c07b91f23 100755 --- a/llvm/utils/git/code-lint-helper.py +++ b/llvm/utils/git/code-lint-helper.py @@ -111,6 +111,19 @@ def filter_changed_files(changed_files: List[str]) -> List[str]: return filtered_files +def filter_doc_files(changed_files: List[str]) -> List[str]: + filtered_files = [] + for filepath in changed_files: + _, ext = os.path.splitext(filepath) + if ext not in (".rst"): + continue + if not filepath.startswith("clang-tools-extra/docs/clang-tidy/checks/"): + continue + if os.path.exists(filepath): + filtered_files.append(filepath) + return filtered_files + + def create_comment_text(warning: str, cpp_files: List[str]) -> str: instructions = get_instructions(cpp_files) return f""" @@ -235,37 +248,13 @@ def run_clang_tidy(changed_files: List[str], args: LintArgs) -> Optional[str]: return clean_clang_tidy_output(proc.stdout.strip()) -def clean_doc8_output(output: str) -> Optional[str]: - if not output: - return None +def get_doc8_instructions(doc_files: List[str]) -> str: + files_str = " ".join(doc_files) + return f"doc8 -q {files_str}" - lines = output.split("\n") - cleaned_lines = [] - in_summary = False - for line in lines: - if line.startswith("Scanning...") or line.startswith("Validating..."): - continue - if line.startswith("========"): - in_summary = True - continue - if in_summary: - continue - if line.strip(): - cleaned_lines.append(line) - - if cleaned_lines: - return "\n".join(cleaned_lines) - return None - - -def get_doc8_instructions() -> str: - # TODO: use git diff - return "doc8 ./clang-tools-extra/docs/clang-tidy/checks/" - - -def create_doc8_comment_text(doc8_output: str) -> str: - instructions = get_doc8_instructions() +def create_doc8_comment_text(doc8_output: str, doc_files: List[str]) -> str: + instructions = get_doc8_instructions(doc_files) return f""" :warning: Documentation linter doc8 found issues in your code. :warning: @@ -293,8 +282,11 @@ def create_doc8_comment_text(doc8_output: str) -> str: """ -def run_doc8(args: LintArgs) -> tuple[int, Optional[str]]: - doc8_cmd = [args.doc8_binary, "./clang-tools-extra/docs/clang-tidy/checks/"] +def run_doc8(doc_files: List[str], args: LintArgs) -> tuple[int, Optional[str]]: + if not doc_files: + return 0, None + + doc8_cmd = [args.doc8_binary, "-q"] + doc_files if args.verbose: print(f"Running doc8: {' '.join(doc8_cmd)}") @@ -307,20 +299,32 @@ def run_doc8(args: LintArgs) -> tuple[int, Optional[str]]: check=False, ) - cleaned_output = clean_doc8_output(proc.stdout.strip()) - if proc.returncode != 0 and cleaned_output is None: + output = proc.stdout.strip() + if proc.returncode != 0 and not output: # Infrastructure failure return proc.returncode, proc.stderr.strip() - return proc.returncode, cleaned_output + return proc.returncode, output if output else None def run_doc8_linter(args: LintArgs) -> tuple[bool, Optional[dict]]: - returncode, result = run_doc8(args) + changed_files = [] + if args.changed_files: + changed_files = args.changed_files.split(',') + doc_files = filter_doc_files(changed_files) + + is_success = True + result = None + + if doc_files: + returncode, result = run_doc8(doc_files, args) + if returncode != 0: + is_success = False + should_update_gh = args.token is not None and args.repo is not None comment = None - if returncode == 0: + if is_success: if should_update_gh: comment_text = ( ":white_check_mark: With the latest revision " @@ -331,7 +335,7 @@ def run_doc8_linter(args: LintArgs) -> tuple[bool, Optional[dict]]: else: if should_update_gh: if result: - comment_text = create_doc8_comment_text(result) + comment_text = create_doc8_comment_text(result, doc_files) comment = create_comment(comment_text, args, create_new=True) else: comment_text = ( >From 09a61e4c17e4f0a1d1cace501c77b83d7e00e117 Mon Sep 17 00:00:00 2001 From: mtx <[email protected]> Date: Fri, 21 Nov 2025 15:53:20 +0800 Subject: [PATCH 06/10] [WIP] Unified script --- .github/workflows/pr-code-lint.yml | 15 +- llvm/utils/git/code-lint-helper.py | 552 ++++++++++++----------------- 2 files changed, 232 insertions(+), 335 deletions(-) diff --git a/.github/workflows/pr-code-lint.yml b/.github/workflows/pr-code-lint.yml index 337a033f4848c..4f016b42d180c 100644 --- a/.github/workflows/pr-code-lint.yml +++ b/.github/workflows/pr-code-lint.yml @@ -81,14 +81,13 @@ jobs: pip install doc8 --break-system-packages echo "$HOME/.local/bin" >> $GITHUB_PATH - - name: Run clang-tidy linter + - name: Run linters env: GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} run: | echo "[]" > comments && python3 llvm/utils/git/code-lint-helper.py \ - --linter clang-tidy \ --token ${{ secrets.GITHUB_TOKEN }} \ --issue-number $GITHUB_PR_NUMBER \ --start-rev HEAD~1 \ @@ -96,18 +95,6 @@ jobs: --verbose \ --changed-files "$CHANGED_FILES" - - name: Run doc8 linter - env: - GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - python3 llvm/utils/git/code-lint-helper.py \ - --linter doc8 \ - --token ${{ secrets.GITHUB_TOKEN }} \ - --issue-number $GITHUB_PR_NUMBER \ - --start-rev HEAD~1 \ - --end-rev HEAD \ - --verbose - - name: Upload results uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 if: always() diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py index 7172c07b91f23..ca4cf481ade2f 100755 --- a/llvm/utils/git/code-lint-helper.py +++ b/llvm/utils/git/code-lint-helper.py @@ -35,7 +35,6 @@ class LintArgs: build_path: str = "build" clang_tidy_binary: str = "clang-tidy" doc8_binary: str = "doc8" - linter: str = None def __init__(self, args: argparse.Namespace = None) -> None: if not args is None: @@ -43,91 +42,45 @@ def __init__(self, args: argparse.Namespace = None) -> None: self.end_rev = args.end_rev self.repo = args.repo self.token = args.token - self.changed_files = args.changed_files + if args.changed_files: + self.changed_files = args.changed_files.split(",") + else: + self.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 self.doc8_binary = args.doc8_binary - self.linter = args.linter - - -COMMENT_TAG_CLANG_TIDY = "<!--LLVM CODE LINT COMMENT: clang-tidy-->" -COMMENT_TAG_DOC8 = "<!--LLVM CODE LINT COMMENT: doc8-->" - - -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 +class LintHelper: + COMMENT_TAG = "<!--LLVM CODE LINT COMMENT: {linter}-->" + name: str + friendly_name: str + comment: dict = None - # 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/") :] + @property + def comment_tag(self) -> str: + return self.COMMENT_TAG.format(linter=self.name) - cleaned_lines.append(line) + @property + def instructions(self) -> str: + raise NotImplementedError() - if cleaned_lines: - return "\n".join(cleaned_lines) - return None + def filter_changed_files(self, changed_files: List[str]) -> List[str]: + raise NotImplementedError() + def run_linter_tool( + self, files_to_lint: List[str], args: LintArgs + ) -> Optional[str]: + raise NotImplementedError() -# TODO: Add more rules when enabling other projects to use clang-tidy in CI. -def should_lint_file(filepath: str) -> bool: - 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 filter_doc_files(changed_files: List[str]) -> List[str]: - filtered_files = [] - for filepath in changed_files: - _, ext = os.path.splitext(filepath) - if ext not in (".rst"): - continue - if not filepath.startswith("clang-tools-extra/docs/clang-tidy/checks/"): - continue - if os.path.exists(filepath): - filtered_files.append(filepath) - return filtered_files - - -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: + def pr_comment_text_for_diff( + self, linter_output: str, files_to_lint: List[str], args: LintArgs + ) -> str: + instructions = self.instructions(files_to_lint, args) + return f""" +:warning: {self.friendly_name}, {self.name} found issues in your code. :warning: <details> <summary> @@ -142,268 +95,238 @@ def create_comment_text(warning: str, cpp_files: List[str]) -> str: <details> <summary> -View the output from clang-tidy here. +View the output from {self.name} here. </summary> ``` -{warning} +{linter_output} ``` </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 find_comment(pr: any, args: LintArgs) -> any: - linter_tag = get_comment_tag(args.linter) - other_linter = "doc8" if args.linter == "clang-tidy" else "clang-tidy" - other_tag = get_comment_tag(other_linter) - - for comment in pr.as_issue().get_comments(): - body = comment.body - if linter_tag in body and other_tag not in body: - # Found a comment that is exclusively for this linter. - return comment - return None - - -def get_comment_tag(linter: str) -> str: - if linter == "clang-tidy": - return COMMENT_TAG_CLANG_TIDY - elif linter == "doc8": - return COMMENT_TAG_DOC8 - raise ValueError(f"Unknown linter: {linter}") - - -def create_comment( - comment_text: str, args: LintArgs, create_new: bool -) -> Optional[dict]: - import github - - repo = github.Github(args.token).get_repo(args.repo) - pr = repo.get_issue(args.issue_number).as_pull_request() - - comment_tag = get_comment_tag(args.linter) - comment_text = comment_tag + "\n\n" + comment_text + def update_pr(self, comment_text: str, args: LintArgs, create_new: bool) -> None: + import github + from github import IssueComment, PullRequest - existing_comment = find_comment(pr, args) + repo = github.Github(args.token).get_repo(args.repo) + pr = repo.get_issue(args.issue_number).as_pull_request() - comment = None - if create_new or existing_comment: - comment = {"body": comment_text} - if existing_comment: - comment["id"] = existing_comment.id - return comment + comment_text = self.comment_tag + "\n\n" + comment_text + existing_comment = self.find_comment(pr) -def run_clang_tidy(changed_files: List[str], args: LintArgs) -> Optional[str]: - if not changed_files: - print("no c/c++ files found") - return None + if create_new or existing_comment: + self.comment = {"body": comment_text} + if existing_comment: + self.comment["id"] = existing_comment.id - 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 diff_proc.returncode != 0: - print(f"Git diff failed: {diff_proc.stderr}") - return None + def run(self, args: LintArgs) -> bool: + files_to_lint = self.filter_changed_files(args.changed_files) - diff_content = diff_proc.stdout - if not diff_content.strip(): - print("No diff content found") - return None + is_success = True + linter_output = None - tidy_diff_cmd = [ - "clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py", - "-path", - args.build_path, - "-p1", - "-quiet", - ] + if files_to_lint: + linter_output = self.run_linter_tool(files_to_lint, args) + if linter_output: + is_success = False - 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, - ) + should_update_gh = args.token is not None and args.repo is not None - return clean_clang_tidy_output(proc.stdout.strip()) + if is_success: + 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 + else: + if should_update_gh: + if linter_output: + comment_text = self.pr_comment_text_for_diff( + linter_output, files_to_lint, args + ) + self.update_pr(comment_text, args, create_new=True) + else: + comment_text = ( + f":warning: The {self.friendly_name} failed without printing " + "an output. Check the logs for output. :warning:" + ) + self.update_pr(comment_text, args, create_new=False) + else: + if linter_output: + print( + f"Warning: {self.friendly_name}, {self.name} detected " + "some issues with your code..." + ) + print(linter_output) + else: + print(f"Warning: {self.friendly_name}, {self.name} failed to run.") + return False + + +class ClangTidyLintHelper(LintHelper): + name = "clang-tidy" + friendly_name = "C/C++ code linter" + + def instructions(self, cpp_files: List[str], args: LintArgs) -> 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 {args.build_path} -p1 -quiet""" + + def filter_changed_files(self, changed_files: List[str]) -> List[str]: + clang_tidy_changed_files = [ + arg for arg in changed_files if "third-party" not in arg + ] + + filtered_files = [] + for filepath in clang_tidy_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 _should_lint_file(self, filepath: str) -> bool: + # TODO: Add more rules when enabling other projects to use clang-tidy in CI. + return filepath.startswith("clang-tools-extra/clang-tidy/") + + def run_linter_tool(self, cpp_files: List[str], args: LintArgs) -> Optional[str]: + if not cpp_files: + return None + + 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 "Git diff failed" -def get_doc8_instructions(doc_files: List[str]) -> str: - files_str = " ".join(doc_files) - return f"doc8 -q {files_str}" + diff_content = diff_proc.stdout + if not diff_content.strip(): + return None + tidy_diff_cmd = [ + "clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py", + "-path", + args.build_path, + "-p1", + "-quiet", + ] -def create_doc8_comment_text(doc8_output: str, doc_files: List[str]) -> str: - instructions = get_doc8_instructions(doc_files) - return f""" -:warning: Documentation linter doc8 found issues in your code. :warning: + 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, + ) -<details> -<summary> -You can test this locally with the following command: -</summary> + clean_output = self._clean_clang_tidy_output(proc.stdout.strip()) + return clean_output -```bash -{instructions} -``` -</details> + def _clean_clang_tidy_output(self, output: str) -> Optional[str]: + if not output or output == "No relevant changes found.": + return None -<details> -<summary> -View the output from doc8 here. -</summary> + lines = output.split("\n") + cleaned_lines = [] -``` -{doc8_output} -``` + for line in lines: + if line.startswith("Running clang-tidy in") or line.endswith("generated."): + continue -</details> -""" + idx = line.rfind("llvm-project/") + if idx != -1: + line = line[idx + len("llvm-project/") :] + cleaned_lines.append(line) -def run_doc8(doc_files: List[str], args: LintArgs) -> tuple[int, Optional[str]]: - if not doc_files: - return 0, None + if cleaned_lines: + return "\n".join(cleaned_lines) + return None - doc8_cmd = [args.doc8_binary, "-q"] + doc_files - if args.verbose: - print(f"Running doc8: {' '.join(doc8_cmd)}") - - proc = subprocess.run( - doc8_cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - check=False, - ) +class Doc8LintHelper(LintHelper): + name = "doc8" + friendly_name = "Documentation linter" - output = proc.stdout.strip() - if proc.returncode != 0 and not output: - # Infrastructure failure - return proc.returncode, proc.stderr.strip() + def instructions(self, doc_files: List[str], args: LintArgs) -> str: + files_str = " ".join(doc_files) + return f"doc8 -q {files_str}" - return proc.returncode, output if output else None + 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 (".rst"): + continue + if not filepath.startswith("clang-tools-extra/docs/clang-tidy/checks/"): + continue + if os.path.exists(filepath): + filtered_files.append(filepath) + return filtered_files + def run_linter_tool(self, doc_files: List[str], args: LintArgs) -> Optional[str]: + if not doc_files: + return None -def run_doc8_linter(args: LintArgs) -> tuple[bool, Optional[dict]]: - changed_files = [] - if args.changed_files: - changed_files = args.changed_files.split(',') - doc_files = filter_doc_files(changed_files) + doc8_cmd = [args.doc8_binary, "-q"] + doc_files - is_success = True - result = None + if args.verbose: + print(f"Running doc8: {' '.join(doc8_cmd)}") + + proc = subprocess.run( + doc8_cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=False, + ) - if doc_files: - returncode, result = run_doc8(doc_files, args) - if returncode != 0: - is_success = False + output = proc.stdout.strip() + if proc.returncode != 0 and not output: + return proc.stderr.strip() - should_update_gh = args.token is not None and args.repo is not None - comment = None - if is_success: - if should_update_gh: - comment_text = ( - ":white_check_mark: With the latest revision " - "this PR passed the documentation linter." - ) - comment = create_comment(comment_text, args, create_new=False) - return True, comment - else: - if should_update_gh: - if result: - comment_text = create_doc8_comment_text(result, doc_files) - comment = create_comment(comment_text, args, create_new=True) - else: - comment_text = ( - ":warning: The documentation linter failed without printing " - "an output. Check the logs for output. :warning:" - ) - comment = create_comment(comment_text, args, create_new=False) - else: - if result: - print( - "Warning: Documentation linter, doc8 detected " - "some issues with your code..." - ) - print(result) - else: - print("Warning: Documentation linter, doc8 failed to run.") - return False, comment - - -def run_clang_tidy_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] - - 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 +ALL_LINTERS = (ClangTidyLintHelper(), Doc8LintHelper()) if __name__ == "__main__": + parser = argparse.ArgumentParser() - parser.add_argument( - "--linter", - type=str, - choices=["clang-tidy", "doc8"], - required=True, - help="The linter to run.", - ) parser.add_argument( "--token", type=str, required=True, help="GitHub authentication token" ) @@ -454,43 +377,30 @@ def run_clang_tidy_linter( args = LintArgs(parsed_args) if args.verbose: - print(f"running linter {args.linter}") + print("Running all linters.") - success, comment = False, None - if args.linter == "clang-tidy": - changed_files = [] - if args.changed_files: - changed_files = args.changed_files.split(",") - if args.verbose: - print(f"got changed files: {changed_files}") - success, comment = run_clang_tidy_linter(changed_files, args) - elif args.linter == "doc8": - success, comment = run_doc8_linter(args) + overall_success = True + all_comments = [] - if not success: + for linter in ALL_LINTERS: if args.verbose: - print(f"linter {args.linter} failed") + print(f"Running linter: {linter.name}") - # Write comments file if we have a comment - if comment: - import json - if args.verbose: - print(f"linter {args.linter} has comment: {comment}") + linter_passed = linter.run(args) + if not linter_passed: + overall_success = False - existing_comments = [] - if os.path.exists("comments"): - with open("comments", "r") as f: - try: - existing_comments = json.load(f) - except json.JSONDecodeError: - # File might be empty or invalid, start fresh - pass + if linter.comment: + all_comments.append(linter.comment) - existing_comments.append(comment) + if len(all_comments): + import json with open("comments", "w") as f: - json.dump(existing_comments, f) + json.dump(all_comments, f) - if not success: - print(f"error: linter {args.linter} failed") + if not overall_success: + print("error: Some linters failed.") sys.exit(1) + else: + print("All linters passed.") >From f3cb4bbc5ce17eac9264b7426b37fec95fddf2c6 Mon Sep 17 00:00:00 2001 From: mtx <[email protected]> Date: Fri, 21 Nov 2025 16:05:30 +0800 Subject: [PATCH 07/10] Force doc8 check to run --- .../docs/clang-tidy/checks/bugprone/unsafe-functions.rst | 1 + llvm/utils/git/code-lint-helper.py | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst index cb7ea415c54b2..6f9f0df8ba3d5 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst @@ -3,6 +3,7 @@ bugprone-unsafe-functions ========================= + Checks for functions that have safer, more secure replacements available, or are considered deprecated due to design flaws. The check heavily relies on the functions from the diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py index ca4cf481ade2f..0d8bfa0432efe 100755 --- a/llvm/utils/git/code-lint-helper.py +++ b/llvm/utils/git/code-lint-helper.py @@ -256,7 +256,6 @@ def run_linter_tool(self, cpp_files: List[str], args: LintArgs) -> Optional[str] clean_output = self._clean_clang_tidy_output(proc.stdout.strip()) return clean_output - def _clean_clang_tidy_output(self, output: str) -> Optional[str]: if not output or output == "No relevant changes found.": return None @@ -325,7 +324,6 @@ def run_linter_tool(self, doc_files: List[str], args: LintArgs) -> Optional[str] if __name__ == "__main__": - parser = argparse.ArgumentParser() parser.add_argument( "--token", type=str, required=True, help="GitHub authentication token" >From 342cae403efc43f2efa79cfbf3cea179c17dbfbb Mon Sep 17 00:00:00 2001 From: mtx <[email protected]> Date: Fri, 21 Nov 2025 16:15:46 +0800 Subject: [PATCH 08/10] ~ --- llvm/utils/git/code-lint-helper.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py index 0d8bfa0432efe..0821c6830deb2 100755 --- a/llvm/utils/git/code-lint-helper.py +++ b/llvm/utils/git/code-lint-helper.py @@ -36,7 +36,7 @@ class LintArgs: clang_tidy_binary: str = "clang-tidy" doc8_binary: str = "doc8" - def __init__(self, args: argparse.Namespace = None) -> None: + def __init__(self, args: argparse.Namespace) -> None: if not args is None: self.start_rev = args.start_rev self.end_rev = args.end_rev @@ -315,9 +315,18 @@ def run_linter_tool(self, doc_files: List[str], args: LintArgs) -> Optional[str] check=False, ) + if proc.returncode == 0: + return None + output = proc.stdout.strip() - if proc.returncode != 0 and not output: - return proc.stderr.strip() + if output: + return output + + error_output = proc.stderr.strip() + if error_output: + return error_output + + return f"doc8 exited with return code {proc.returncode} but no output." ALL_LINTERS = (ClangTidyLintHelper(), Doc8LintHelper()) >From f8a1c2353e8d1793c92b0ffc84f8cf28182434bc Mon Sep 17 00:00:00 2001 From: mtx <[email protected]> Date: Fri, 21 Nov 2025 21:13:01 +0800 Subject: [PATCH 09/10] ~ --- .../checks/bugprone/unsafe-functions.rst | 1 + llvm/utils/git/code-lint-helper.py | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst index 6f9f0df8ba3d5..7e5938dbc1cf0 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst @@ -17,6 +17,7 @@ The check implements the following rules from the CERT C Coding Standard: `cert-msc24-c` and `cert-msc33-c` redirect here as aliases of this check. + Unsafe functions ---------------- diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py index 0821c6830deb2..58c1eccd4ddb0 100755 --- a/llvm/utils/git/code-lint-helper.py +++ b/llvm/utils/git/code-lint-helper.py @@ -105,10 +105,20 @@ def pr_comment_text_for_diff( </details> """ + # TODO: Refactor this def find_comment(self, pr: any) -> any: + all_linter_names = [l.name for l in ALL_LINTERS] + other_linter_names = [name for name in all_linter_names if name != self.name] + + other_tags = [ + self.COMMENT_TAG.format(linter=name) for name in other_linter_names + ] + for comment in pr.as_issue().get_comments(): - if self.comment_tag in comment.body: - return comment + body = comment.body + if self.comment_tag in body: + if not any(other_tag in body for other_tag in other_tags): + return comment return None def update_pr(self, comment_text: str, args: LintArgs, create_new: bool) -> None: >From f553d45b4ae95c79f67857d896e4c48fae468326 Mon Sep 17 00:00:00 2001 From: mtx <[email protected]> Date: Fri, 21 Nov 2025 23:13:57 +0800 Subject: [PATCH 10/10] Fix script logic and intentionally break another rst file --- .../clang-tidy/checks/abseil/cleanup-ctad.rst | 3 +- llvm/utils/git/code-lint-helper.py | 52 ++++++++++++++----- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/abseil/cleanup-ctad.rst b/clang-tools-extra/docs/clang-tidy/checks/abseil/cleanup-ctad.rst index b8afc8b8a8481..1aecf9af64a00 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/abseil/cleanup-ctad.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/abseil/cleanup-ctad.rst @@ -3,8 +3,7 @@ abseil-cleanup-ctad =================== -Suggests switching the initialization pattern of ``absl::Cleanup`` -instances from the factory function to class template argument +Suggests switching the initialization pattern of ``absl::Cleanup`` instances from the factor function to class template argument deduction (CTAD), in C++17 and higher. .. code-block:: c++ diff --git a/llvm/utils/git/code-lint-helper.py b/llvm/utils/git/code-lint-helper.py index 58c1eccd4ddb0..411231c772215 100755 --- a/llvm/utils/git/code-lint-helper.py +++ b/llvm/utils/git/code-lint-helper.py @@ -18,6 +18,7 @@ """ import argparse +from operator import attrgetter import os import subprocess import sys @@ -107,7 +108,7 @@ def pr_comment_text_for_diff( # TODO: Refactor this def find_comment(self, pr: any) -> any: - all_linter_names = [l.name for l in ALL_LINTERS] + all_linter_names = list(map(attrgetter("name"), ALL_LINTERS)) other_linter_names = [name for name in all_linter_names if name != self.name] other_tags = [ @@ -116,9 +117,10 @@ def find_comment(self, pr: any) -> any: for comment in pr.as_issue().get_comments(): body = comment.body - if self.comment_tag in body: - if not any(other_tag in body for other_tag in other_tags): - return comment + if self.comment_tag in body and not any( + other_tag in body for other_tag in other_tags + ): + return comment return None def update_pr(self, comment_text: str, args: LintArgs, create_new: bool) -> None: @@ -139,8 +141,14 @@ def update_pr(self, comment_text: str, args: LintArgs, create_new: bool) -> None def run(self, args: LintArgs) -> bool: + if args.verbose: + print(f"got changed files: {args.changed_files}") + files_to_lint = self.filter_changed_files(args.changed_files) + if not files_to_lint and args.verbose: + print("no modified files found") + is_success = True linter_output = None @@ -237,10 +245,12 @@ def run_linter_tool(self, cpp_files: List[str], args: LintArgs) -> Optional[str] if diff_proc.returncode != 0: print(f"Git diff failed: {diff_proc.stderr}") - return "Git diff failed" + return None diff_content = diff_proc.stdout if not diff_content.strip(): + if args.verbose: + print("No diff content found") return None tidy_diff_cmd = [ @@ -290,7 +300,7 @@ def _clean_clang_tidy_output(self, output: str) -> Optional[str]: class Doc8LintHelper(LintHelper): name = "doc8" - friendly_name = "Documentation linter" + friendly_name = "documentation linter" def instructions(self, doc_files: List[str], args: LintArgs) -> str: files_str = " ".join(doc_files) @@ -393,31 +403,45 @@ def run_linter_tool(self, doc_files: List[str], args: LintArgs) -> Optional[str] parsed_args = parser.parse_args() args = LintArgs(parsed_args) - if args.verbose: - print("Running all linters.") - overall_success = True + failed_linters = [] all_comments = [] for linter in ALL_LINTERS: if args.verbose: - print(f"Running linter: {linter.name}") + print(f"running linter {linter.name}") linter_passed = linter.run(args) if not linter_passed: overall_success = False + failed_linters.append(linter.name) + if args.verbose: + print(f"linter {linter.name} failed") if linter.comment: + if args.verbose: + print(f"linter {linter.name} has comment: {linter.comment}") all_comments.append(linter.comment) if len(all_comments): import json + existing_comments = [] + if os.path.exists("comments"): + try: + with open("comments", "r") as f: + existing_comments = json.load(f) + if not isinstance(existing_comments, list): + existing_comments = [] + except Exception: + existing_comments = [] + + existing_comments.extend(all_comments) + with open("comments", "w") as f: - json.dump(all_comments, f) + json.dump(existing_comments, f) if not overall_success: - print("error: Some linters failed.") + for name in failed_linters: + print(f"error: linter {name} failed") sys.exit(1) - else: - print("All linters passed.") _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
