From 0595440685f9a01a25b6147150bcc284cf6be0ea Mon Sep 17 00:00:00 2001 From: Douglas Barker Date: Sun, 23 Nov 2025 17:37:50 -0500 Subject: [PATCH 1/3] upgrade to clang-tidy 20 --- .clang-tidy | 2 +- .devcontainer/Dockerfile.dev | 11 ++++++++--- .github/workflows/clang-tidy.yaml | 11 +++++------ ci/do_ci.sh | 4 +++- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 452fbaf014..599a02fd88 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -38,4 +38,4 @@ Checks: > -cppcoreguidelines-macro-usage, -cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-avoid-non-const-global-variables, - -cppcoreguidelines-pro-* \ No newline at end of file + -cppcoreguidelines-pro-* diff --git a/.devcontainer/Dockerfile.dev b/.devcontainer/Dockerfile.dev index 60efed9723..fec4e16c6a 100644 --- a/.devcontainer/Dockerfile.dev +++ b/.devcontainer/Dockerfile.dev @@ -15,13 +15,18 @@ COPY ci /opt/ci RUN apt update && apt install -y wget \ ninja-build \ - llvm-dev \ - libclang-dev \ - clang-tidy \ + llvm-20-dev \ + libclang-20-dev \ + clang-tidy-20 \ shellcheck \ sudo \ cmake +RUN update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-20 200 && \ + update-alternatives --install /usr/bin/llvm-config llvm-config /usr/bin/llvm-config-20 200 && \ + update-alternatives --config clang-tidy && \ + update-alternatives --config llvm-config + RUN cd /opt/ci && bash setup_ci_environment.sh RUN cd /opt/ci && bash install_iwyu.sh diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index 16623b4b28..1a7d6f8c85 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -53,12 +53,11 @@ jobs: run: | sudo -E ./ci/install_thirdparty.sh --install-dir /usr/local --tags-file third_party_release --packages "ryml" - - name: Check clang-tidy + - name: Install clang-tidy-20 run: | - if ! command -v clang-tidy &> /dev/null; then - echo "clang-tidy could not be found" - exit 1 - fi + sudo apt install -y clang-tidy-20 + sudo update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-20 200 + sudo update-alternatives --config clang-tidy echo "Using clang-tidy version: $(clang-tidy --version)" echo "clang-tidy installed at: $(which clang-tidy)" @@ -75,7 +74,7 @@ jobs: -DWITH_OPENTRACING=OFF \ -DCMAKE_CXX_FLAGS="-Wno-deprecated-declarations" \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ - -DCMAKE_CXX_CLANG_TIDY="clang-tidy;--quiet;-p;build-${{ matrix.cmake_options }}" + -DCMAKE_CXX_CLANG_TIDY="clang-tidy;--header-filter=.*;--exclude-header-filter=.*internal/absl/.*;--quiet" - name: Run clang-tidy run: | diff --git a/ci/do_ci.sh b/ci/do_ci.sh index ddeffe74da..0c609a8afb 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -342,6 +342,8 @@ elif [[ "$1" == "cmake.clang_tidy.test" ]]; then cd "${BUILD_DIR}" rm -rf * export BUILD_ROOT="${BUILD_DIR}" + clang-tidy --version + cmake -S ${SRC_DIR} \ -B ${BUILD_DIR} \ -C ${SRC_DIR}/test_common/cmake/all-options-abiv2-preview.cmake \ @@ -349,7 +351,7 @@ elif [[ "$1" == "cmake.clang_tidy.test" ]]; then -DWITH_OPENTRACING=OFF \ -DCMAKE_CXX_FLAGS="-Wno-deprecated-declarations" \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ - -DCMAKE_CXX_CLANG_TIDY="clang-tidy;--quiet;-p;${BUILD_DIR}" + -DCMAKE_CXX_CLANG_TIDY="clang-tidy;--header-filter=.*;--exclude-header-filter=.*internal/absl/.*;--quiet" make -j $(nproc) make test exit 0 From 3de21e8cbfe8fcdd9e1509e33879f61b54517af4 Mon Sep 17 00:00:00 2001 From: Douglas Barker Date: Mon, 24 Nov 2025 00:34:22 -0500 Subject: [PATCH 2/3] add python script to create a markdown report for clang-tidy warnings. Filter out generated third-party headers --- .github/workflows/clang-tidy.yaml | 58 +++++++--- ci/create_clang_tidy_report.py | 172 ++++++++++++++++++++++++++++++ ci/do_ci.sh | 13 ++- 3 files changed, 223 insertions(+), 20 deletions(-) create mode 100644 ci/create_clang_tidy_report.py diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index 1a7d6f8c85..2f5f99ef13 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -67,37 +67,63 @@ jobs: CXX: clang++ run: | echo "Running cmake..." - cmake -B build-${{ matrix.cmake_options }} \ + cmake + -S . \ + -B build-${{ matrix.cmake_options }} \ -C ./test_common/cmake/${{ matrix.cmake_options }}.cmake \ -DCMAKE_CXX_STANDARD=14 \ -DWITH_STL=CXX14 \ -DWITH_OPENTRACING=OFF \ -DCMAKE_CXX_FLAGS="-Wno-deprecated-declarations" \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ - -DCMAKE_CXX_CLANG_TIDY="clang-tidy;--header-filter=.*;--exclude-header-filter=.*internal/absl/.*;--quiet" + -DCMAKE_CXX_CLANG_TIDY="clang-tidy;--header-filter=.*/opentelemetry-cpp/.*;--exclude-header-filter=.*(internal/absl|third_party|third-party)/.*;--quiet" - - name: Run clang-tidy + - name: Run clang-tidy and Build + id: build run: | - cmake --build build-${{ matrix.cmake_options }} -- -j$(nproc) 2>&1 | tee clang-tidy-${{ matrix.cmake_options }}.log + BUILD_LOG=clang-tidy-${{ matrix.cmake_options }}.log + set -o pipefail + cmake --build build-${{ matrix.cmake_options }} -- -j$(nproc) 2>&1 | tee $BUILD_LOG + echo "build_log=$BUILD_LOG" >> "$GITHUB_OUTPUT" - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + - name: Analyze clang-tidy build log + id: analyze + run: | + SCRIPT_OUTPUT=$(python3 ./ci/create_clang_tidy_report.py \ + --job_name ${{ matrix.cmake_options }} \ + --build_log clang-tidy-${{ matrix.cmake_options }}.log \ + --output ./clang_tidy_report-${{ matrix.cmake_options }}.md) + export $SCRIPT_OUTPUT + echo "Found $TOTAL_WARNINGS unique warnings" + echo "clang-tidy report generated at $REPORT_PATH" + echo "warning_count=$TOTAL_WARNINGS" >> "$GITHUB_OUTPUT" + echo "report_path=$REPORT_PATH" >> "$GITHUB_OUTPUT" + cat $REPORT_PATH >> $GITHUB_STEP_SUMMARY + + - name: Upload build log + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: Logs-clang-tidy-${{ matrix.cmake_options }} - path: ./clang-tidy-${{ matrix.cmake_options }}.log + path: ${{ steps.build.outputs.build_log }} + + - name: Upload warning report + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: Report-clang-tidy-${{ matrix.cmake_options }} + path: ${{ steps.analyze.outputs.report_path }} - - name: Count warnings + - name: Check Warning Limits run: | - COUNT=$(grep -c "warning:" clang-tidy-${{ matrix.cmake_options }}.log) - echo "clang-tidy reported ${COUNT} warning(s) with cmake options preset '${{ matrix.cmake_options }}'" + readonly COUNT="${{ steps.analyze.outputs.warning_count }}" + readonly LIMIT="${{ matrix.warning_limit }}" - readonly WARNING_LIMIT=${{ matrix.warning_limit }} + echo "clang-tidy reported ${COUNT} unique warning(s) with preset '${{ matrix.cmake_options }}'" + echo "Limit is ${LIMIT}" - # FAIL the build if COUNT > WARNING_LIMIT - if [ $COUNT -gt $WARNING_LIMIT ] ; then - echo "clang-tidy reported ${COUNT} warning(s) exceeding the existing warning limit of ${WARNING_LIMIT} with cmake options preset '${{ matrix.cmake_options }}'" + if [ "$COUNT" -gt "$LIMIT" ]; then + echo "::error::clang-tidy reported ${COUNT} warning(s) exceeding the limit of ${LIMIT}" exit 1 - # WARN in annotations if COUNT > 0 - elif [ $COUNT -gt 0 ] ; then - echo "::warning::clang-tidy reported ${COUNT} warning(s) with cmake options preset '${{ matrix.cmake_options }}'" + elif [ "$COUNT" -gt 0 ]; then + echo "::warning::clang-tidy reported ${COUNT} warning(s) within the limit of ${LIMIT}" fi diff --git a/ci/create_clang_tidy_report.py b/ci/create_clang_tidy_report.py new file mode 100644 index 0000000000..c66840b09a --- /dev/null +++ b/ci/create_clang_tidy_report.py @@ -0,0 +1,172 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import re +import sys +from collections import defaultdict +from enum import Enum +from pathlib import Path +from typing import Dict, List, NamedTuple, Optional, Set + +# --- Configuration --- +REPO_NAME = "opentelemetry-cpp" +MAX_ROWS = 1000 +WARNING_RE = re.compile( + r"^(?P.+):(?P\d+):(?P\d+): warning: (?P.+) " + r"\[(?P.+)\]$" +) +ANSI_RE = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") + + +class OutputKeys(str, Enum): + TOTAL_WARNINGS = "TOTAL_WARNINGS" + REPORT_PATH = "REPORT_PATH" + + +class ClangTidyWarning(NamedTuple): + file: str + line: int + col: int + msg: str + check: str + + +def clean_path(path: str) -> str: + """Strip path prefix to make it relative to the repo or CWD.""" + if f"{REPO_NAME}/" in path: + return path.split(f"{REPO_NAME}/", 1)[1] + try: + return str(Path(path).relative_to(Path.cwd())) + except ValueError: + return path + + +def parse_log(log_path: Path) -> Set[ClangTidyWarning]: + if not log_path.exists(): + sys.exit(f"[ERROR] Log not found: {log_path}") + unique = set() + with log_path.open("r", encoding="utf-8", errors="replace") as f: + for line in f: + line = ANSI_RE.sub("", line.strip()) + if "warning:" not in line: + continue + match = WARNING_RE.match(line) + if match: + unique.add( + ClangTidyWarning( + clean_path(match.group("file")), + int(match.group("line")), + int(match.group("col")), + match.group("msg"), + match.group("check"), + ) + ) + return unique + + +def generate_report( + warnings: Set[ClangTidyWarning], + output_path: Path, + job_name: Optional[str] = None, +): + by_check: Dict[str, List[ClangTidyWarning]] = defaultdict(list) + by_file: Dict[str, List[ClangTidyWarning]] = defaultdict(list) + + for w in warnings: + by_check[w.check].append(w) + by_file[w.file].append(w) + + with output_path.open("w", encoding="utf-8") as md: + title = "# " + if job_name: + title += f"{job_name}" + + md.write( + f"{title} `clang-tidy` job \t[**{len(warnings)} warnings**]\n\n" + ) + md.write(f"
{'Warnings breakdown'} - Click to expand\n\n") + + def write_section( + title, data, item_sort_key, header, row_fmt, group_key, reverse + ): + md.write(f"## {title}\n") + sorted_groups = sorted( + data.items(), key=group_key, reverse=reverse + ) + for key, items in sorted_groups: + md.write( + f"
{key} ({len(items)})" + f"\n\n{header}\n" + ) + for i, w in enumerate(sorted(items, key=item_sort_key)): + if i >= MAX_ROWS: + remaining = len(items) - i + md.write( + f"| ... | ... | *{remaining} more omitted...* |\n" + ) + break + md.write(row_fmt(w) + "\n") + md.write("\n
\n\n") + + # Warnings by File: Sorted Alphabetically + write_section( + "Warnings by File", + by_file, + item_sort_key=lambda w: w.line, + header="| Line | Check | Message |\n|---|---|---|", + row_fmt=lambda w: f"| {w.line} | `{w.check}` | {w.msg} |", + group_key=lambda x: x[0], + reverse=False, + ) + + # Warnings by clang-tidy check: Sort by Warning count + write_section( + "Warnings by `clang-tidy` Check", + by_check, + item_sort_key=lambda w: (w.file, w.line), + header="| File | Line | Message |\n|---|---|---|", + row_fmt=lambda w: f"| `{w.file}` | {w.line} | {w.msg} |", + group_key=lambda x: len(x[1]), + reverse=True, + ) + + md.write(f"
\n") + md.write("\n----\n") + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-l", + "--build_log", + type=Path, + required=True, + help="Clang-tidy log file", + ) + parser.add_argument( + "-o", + "--output", + type=Path, + default="clang_tidy_report.md", + help="Output report path", + ) + parser.add_argument( + "-j", + "--job_name", + type=str, + help="Job name to include in the report title", + ) + args = parser.parse_args() + + warnings = parse_log(args.build_log) + generate_report(warnings, args.output, args.job_name) + + sys.stdout.write(f"{OutputKeys.TOTAL_WARNINGS.value}={len(warnings)}\n") + if args.output.exists(): + sys.stdout.write(f"{OutputKeys.REPORT_PATH.value}={args.output.resolve()}\n") + else: + sys.exit(f"[ERROR] Failed to write report: {args.output.resolve()}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 0c609a8afb..714a284f89 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -341,9 +341,8 @@ elif [[ "$1" == "cmake.legacy.test" ]]; then elif [[ "$1" == "cmake.clang_tidy.test" ]]; then cd "${BUILD_DIR}" rm -rf * - export BUILD_ROOT="${BUILD_DIR}" clang-tidy --version - + LOG_FILE="${BUILD_DIR}/opentelemetry-cpp-clang-tidy.log" cmake -S ${SRC_DIR} \ -B ${BUILD_DIR} \ -C ${SRC_DIR}/test_common/cmake/all-options-abiv2-preview.cmake \ @@ -351,9 +350,15 @@ elif [[ "$1" == "cmake.clang_tidy.test" ]]; then -DWITH_OPENTRACING=OFF \ -DCMAKE_CXX_FLAGS="-Wno-deprecated-declarations" \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ - -DCMAKE_CXX_CLANG_TIDY="clang-tidy;--header-filter=.*;--exclude-header-filter=.*internal/absl/.*;--quiet" - make -j $(nproc) + -DCMAKE_CXX_CLANG_TIDY="clang-tidy;--header-filter=.*/opentelemetry-cpp/.*;--exclude-header-filter=.*(internal/absl|third_party|third-party)/.*;--quiet" + make -j $(nproc) 2>&1 | tee "$LOG_FILE" make test + SCRIPT_OUTPUT=$(python3 ${SRC_DIR}/ci/create_clang_tidy_report.py \ + --build_log "$LOG_FILE" \ + --output clang_tidy_report.md) + export $SCRIPT_OUTPUT + echo "total warnings = $TOTAL_WARNINGS" + echo "clang-tidy report generated at $REPORT_PATH" exit 0 elif [[ "$1" == "cmake.legacy.exporter.otprotocol.test" ]]; then cd "${BUILD_DIR}" From 4dc631accd02ef4e296728cec7debd478eeb7ae8 Mon Sep 17 00:00:00 2001 From: Douglas Barker Date: Mon, 24 Nov 2025 00:39:05 -0500 Subject: [PATCH 3/3] ci fix --- .github/workflows/clang-tidy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index 2f5f99ef13..8d41e58c24 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -67,7 +67,7 @@ jobs: CXX: clang++ run: | echo "Running cmake..." - cmake + cmake \ -S . \ -B build-${{ matrix.cmake_options }} \ -C ./test_common/cmake/${{ matrix.cmake_options }}.cmake \