Skip to content

Commit 5fc4707

Browse files
authored
[CI] upgrade to clang-tidy 20 (open-telemetry#3762)
1 parent 6f1024d commit 5fc4707

File tree

5 files changed

+260
-46
lines changed

5 files changed

+260
-46
lines changed

.clang-tidy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@ Checks: >
3838
-cppcoreguidelines-macro-usage,
3939
-cppcoreguidelines-non-private-member-variables-in-classes,
4040
-cppcoreguidelines-avoid-non-const-global-variables,
41-
-cppcoreguidelines-pro-*
41+
-cppcoreguidelines-pro-*

.devcontainer/Dockerfile.dev

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@ COPY ci /opt/ci
1515

1616
RUN apt update && apt install -y wget \
1717
ninja-build \
18-
llvm-dev \
19-
libclang-dev \
20-
clang-tidy \
18+
llvm-20-dev \
19+
libclang-20-dev \
20+
clang-tidy-20 \
2121
shellcheck \
2222
sudo \
2323
cmake
2424

25+
RUN update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-20 200 && \
26+
update-alternatives --install /usr/bin/llvm-config llvm-config /usr/bin/llvm-config-20 200 && \
27+
update-alternatives --config clang-tidy && \
28+
update-alternatives --config llvm-config
29+
2530
RUN cd /opt/ci && bash setup_ci_environment.sh
2631
RUN cd /opt/ci && bash install_iwyu.sh
2732

.github/workflows/clang-tidy.yaml

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@ jobs:
1717
matrix:
1818
include:
1919
- cmake_options: all-options-abiv1-preview
20-
warning_limit: 63
20+
warning_limit: 595
2121
- cmake_options: all-options-abiv2-preview
22-
warning_limit: 63
22+
warning_limit: 597
23+
env:
24+
CC: /usr/bin/clang-18
25+
CXX: /usr/bin/clang++-18
26+
CXX_STANDARD: '14' # Run clang-tidy on the minimum supported c++ standard
2327
steps:
2428
- name: Harden the runner (Audit all outbound calls)
2529
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
@@ -53,52 +57,60 @@ jobs:
5357
run: |
5458
sudo -E ./ci/install_thirdparty.sh --install-dir /usr/local --tags-file third_party_release --packages "ryml"
5559
56-
- name: Check clang-tidy
60+
- name: Install clang-tidy-20
5761
run: |
58-
if ! command -v clang-tidy &> /dev/null; then
59-
echo "clang-tidy could not be found"
60-
exit 1
61-
fi
62+
sudo apt install -y clang-tidy-20
63+
sudo update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-20 200
64+
sudo update-alternatives --config clang-tidy
6265
echo "Using clang-tidy version: $(clang-tidy --version)"
6366
echo "clang-tidy installed at: $(which clang-tidy)"
6467
65-
- name: Prepare CMake
68+
- name: Build and run clang-tidy
69+
id: build
6670
env:
67-
CC: clang
68-
CXX: clang++
71+
OTELCPP_CMAKE_CACHE_FILE: ${{ matrix.cmake_options }}.cmake
72+
BUILD_DIR: build-${{ matrix.cmake_options }}
6973
run: |
70-
echo "Running cmake..."
71-
cmake -B build-${{ matrix.cmake_options }} \
72-
-C ./test_common/cmake/${{ matrix.cmake_options }}.cmake \
73-
-DCMAKE_CXX_STANDARD=14 \
74-
-DWITH_STL=CXX14 \
75-
-DWITH_OPENTRACING=OFF \
76-
-DCMAKE_CXX_FLAGS="-Wno-deprecated-declarations" \
77-
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
78-
-DCMAKE_CXX_CLANG_TIDY="clang-tidy;--quiet;-p;build-${{ matrix.cmake_options }}"
74+
./ci/do_ci.sh cmake.clang_tidy.test
75+
echo "build_log=${BUILD_DIR}/opentelemetry-cpp-clang-tidy.log" >> "$GITHUB_OUTPUT"
7976
80-
- name: Run clang-tidy
77+
- name: Analyze clang-tidy output
78+
id: analyze
8179
run: |
82-
cmake --build build-${{ matrix.cmake_options }} -- -j$(nproc) 2>&1 | tee clang-tidy-${{ matrix.cmake_options }}.log
80+
SCRIPT_OUTPUT=$(python3 ./ci/create_clang_tidy_report.py \
81+
--build_log ${{ steps.build.outputs.build_log }} \
82+
--output ./clang_tidy_report-${{ matrix.cmake_options }}.md)
83+
export $SCRIPT_OUTPUT
84+
echo "Found $TOTAL_WARNINGS unique warnings"
85+
echo "clang-tidy report generated at $REPORT_PATH"
86+
echo "warning_count=$TOTAL_WARNINGS" >> "$GITHUB_OUTPUT"
87+
echo "report_path=$REPORT_PATH" >> "$GITHUB_OUTPUT"
88+
cat $REPORT_PATH >> $GITHUB_STEP_SUMMARY
8389
84-
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
90+
- name: Upload build log
91+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
8592
with:
8693
name: Logs-clang-tidy-${{ matrix.cmake_options }}
87-
path: ./clang-tidy-${{ matrix.cmake_options }}.log
94+
path: ${{ steps.build.outputs.build_log }}
95+
96+
- name: Upload warning report
97+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
98+
with:
99+
name: Report-clang-tidy-${{ matrix.cmake_options }}
100+
path: ${{ steps.analyze.outputs.report_path }}
88101

89-
- name: Count warnings
102+
- name: Check Warning Limits
90103
run: |
91-
COUNT=$(grep -c "warning:" clang-tidy-${{ matrix.cmake_options }}.log)
92-
echo "clang-tidy reported ${COUNT} warning(s) with cmake options preset '${{ matrix.cmake_options }}'"
104+
readonly COUNT="${{ steps.analyze.outputs.warning_count }}"
105+
readonly LIMIT="${{ matrix.warning_limit }}"
93106
94-
readonly WARNING_LIMIT=${{ matrix.warning_limit }}
107+
echo "clang-tidy reported ${COUNT} unique warning(s) with preset '${{ matrix.cmake_options }}'"
108+
echo "Limit is ${LIMIT}"
95109
96-
# FAIL the build if COUNT > WARNING_LIMIT
97-
if [ $COUNT -gt $WARNING_LIMIT ] ; then
98-
echo "clang-tidy reported ${COUNT} warning(s) exceeding the existing warning limit of ${WARNING_LIMIT} with cmake options preset '${{ matrix.cmake_options }}'"
110+
if [ "$COUNT" -gt "$LIMIT" ]; then
111+
echo "::error::clang-tidy reported ${COUNT} warning(s) exceeding the limit of ${LIMIT}"
99112
exit 1
100-
# WARN in annotations if COUNT > 0
101-
elif [ $COUNT -gt 0 ] ; then
102-
echo "::warning::clang-tidy reported ${COUNT} warning(s) with cmake options preset '${{ matrix.cmake_options }}'"
113+
elif [ "$COUNT" -gt 0 ]; then
114+
echo "::warning::clang-tidy reported ${COUNT} warning(s) within the limit of ${LIMIT}"
103115
fi
104116

ci/create_clang_tidy_report.py

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# Copyright The OpenTelemetry Authors
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import argparse
5+
import re
6+
import sys
7+
from collections import defaultdict
8+
from enum import Enum
9+
from pathlib import Path
10+
from typing import Dict, List, NamedTuple, Optional, Set
11+
12+
# --- Configuration ---
13+
REPO_NAME = "opentelemetry-cpp"
14+
MAX_ROWS = 1000
15+
WARNING_RE = re.compile(
16+
r"^(?P<file>.+):(?P<line>\d+):(?P<col>\d+): warning: (?P<msg>.+) "
17+
r"\[(?P<check>.+)\]$"
18+
)
19+
ANSI_RE = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
20+
21+
22+
class OutputKeys(str, Enum):
23+
TOTAL_WARNINGS = "TOTAL_WARNINGS"
24+
REPORT_PATH = "REPORT_PATH"
25+
26+
27+
class ClangTidyWarning(NamedTuple):
28+
file: str
29+
line: int
30+
col: int
31+
msg: str
32+
check: str
33+
34+
35+
def clean_path(path: str) -> str:
36+
"""Strip path prefix to make it relative to the repo or CWD."""
37+
if f"{REPO_NAME}/" in path:
38+
return path.split(f"{REPO_NAME}/", 1)[1]
39+
try:
40+
return str(Path(path).relative_to(Path.cwd()))
41+
except ValueError:
42+
return path
43+
44+
45+
def parse_log(log_path: Path) -> Set[ClangTidyWarning]:
46+
if not log_path.exists():
47+
sys.exit(f"[ERROR] Log not found: {log_path}")
48+
unique = set()
49+
with log_path.open("r", encoding="utf-8", errors="replace") as f:
50+
for line in f:
51+
line = ANSI_RE.sub("", line.strip())
52+
if "warning:" not in line:
53+
continue
54+
match = WARNING_RE.match(line)
55+
if match:
56+
unique.add(
57+
ClangTidyWarning(
58+
clean_path(match.group("file")),
59+
int(match.group("line")),
60+
int(match.group("col")),
61+
match.group("msg"),
62+
match.group("check"),
63+
)
64+
)
65+
return unique
66+
67+
68+
def generate_report(
69+
warnings: Set[ClangTidyWarning],
70+
output_path: Path,
71+
):
72+
by_check: Dict[str, List[ClangTidyWarning]] = defaultdict(list)
73+
by_file: Dict[str, List[ClangTidyWarning]] = defaultdict(list)
74+
75+
for w in warnings:
76+
by_check[w.check].append(w)
77+
by_file[w.file].append(w)
78+
79+
with output_path.open("w", encoding="utf-8") as md:
80+
title = "#### "
81+
md.write(
82+
f"{title} `clang-tidy` job reported {len(warnings)} warnings\n\n"
83+
)
84+
85+
if not warnings:
86+
return
87+
88+
def write_section(
89+
title,
90+
data,
91+
item_sort_key,
92+
header,
93+
row_fmt,
94+
group_key,
95+
reverse,
96+
summary_col_name,
97+
):
98+
md.write(f"<details><summary><b>{title}</b><i> - Click to expand </i></summary>\n\n")
99+
sorted_groups = sorted(
100+
data.items(), key=group_key, reverse=reverse
101+
)
102+
103+
# Summary Table (Sorted by Count Descending)
104+
summary_groups = sorted(
105+
data.items(), key=lambda x: len(x[1]), reverse=True
106+
)
107+
md.write("#### Summary\n\n")
108+
md.write(f"| {summary_col_name} | Count |\n|---|---|\n")
109+
for key, items in summary_groups:
110+
md.write(f"| {key} | {len(items)} |\n")
111+
md.write("\n")
112+
113+
md.write("#### Details\n\n")
114+
for key, items in sorted_groups:
115+
md.write(
116+
f"\n----\n\n**{key}** ({len(items)} warnings)\n\n{header}\n"
117+
)
118+
for i, w in enumerate(sorted(items, key=item_sort_key)):
119+
if i >= MAX_ROWS:
120+
remaining = len(items) - i
121+
md.write(
122+
f"| ... | ... | *{remaining} more omitted...* |\n"
123+
)
124+
break
125+
md.write(row_fmt(w) + "\n")
126+
md.write("\n")
127+
md.write("</details>\n\n")
128+
129+
# Warnings by File: Sorted Alphabetically
130+
write_section(
131+
"Warnings by File",
132+
by_file,
133+
item_sort_key=lambda w: w.line,
134+
header="| Line | Check | Message |\n|---|---|---|",
135+
row_fmt=lambda w: f"| {w.line} | `{w.check}` | {w.msg} |",
136+
group_key=lambda x: x[0],
137+
reverse=False,
138+
summary_col_name="File",
139+
)
140+
141+
# Warnings by clang-tidy check: Sort by Warning count
142+
write_section(
143+
"Warnings by clang-tidy Check",
144+
by_check,
145+
item_sort_key=lambda w: (w.file, w.line),
146+
header="| File | Line | Message |\n|---|---|---|",
147+
row_fmt=lambda w: f"| `{w.file}` | {w.line} | {w.msg} |",
148+
group_key=lambda x: len(x[1]),
149+
reverse=True,
150+
summary_col_name="Check",
151+
)
152+
153+
md.write("\n----\n")
154+
155+
156+
def main():
157+
parser = argparse.ArgumentParser()
158+
parser.add_argument(
159+
"-l",
160+
"--build_log",
161+
type=Path,
162+
required=True,
163+
help="Clang-tidy log file",
164+
)
165+
parser.add_argument(
166+
"-o",
167+
"--output",
168+
type=Path,
169+
default="clang_tidy_report.md",
170+
help="Output report path",
171+
)
172+
args = parser.parse_args()
173+
174+
warnings = parse_log(args.build_log)
175+
generate_report(warnings, args.output)
176+
177+
sys.stdout.write(f"{OutputKeys.TOTAL_WARNINGS.value}={len(warnings)}\n")
178+
if args.output.exists():
179+
sys.stdout.write(f"{OutputKeys.REPORT_PATH.value}={args.output.resolve()}\n")
180+
else:
181+
sys.exit(f"[ERROR] Failed to write report: {args.output.resolve()}")
182+
183+
if __name__ == "__main__":
184+
main()

ci/do_ci.sh

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ if [ -n "$CMAKE_TOOLCHAIN_FILE" ]; then
106106
CMAKE_OPTIONS+=("-DCMAKE_TOOLCHAIN_FILE=$CMAKE_TOOLCHAIN_FILE")
107107
fi
108108

109+
if [ -n "$OTELCPP_CMAKE_CACHE_FILE" ]; then
110+
OTELCPP_CMAKE_CACHE_FILE_PATH="${SRC_DIR}/test_common/cmake/${OTELCPP_CMAKE_CACHE_FILE}"
111+
else
112+
OTELCPP_CMAKE_CACHE_FILE_PATH="${SRC_DIR}/test_common/cmake/all-options-abiv1-preview.cmake"
113+
fi
114+
109115
echo "CMAKE_OPTIONS:" "${CMAKE_OPTIONS[@]}"
110116

111117
export CTEST_OUTPUT_ON_FAILURE=1
@@ -339,19 +345,26 @@ elif [[ "$1" == "cmake.legacy.test" ]]; then
339345
make test
340346
exit 0
341347
elif [[ "$1" == "cmake.clang_tidy.test" ]]; then
342-
cd "${BUILD_DIR}"
343-
rm -rf *
344-
export BUILD_ROOT="${BUILD_DIR}"
345-
cmake -S ${SRC_DIR} \
348+
rm -rf "${BUILD_DIR}"
349+
mkdir -p "${BUILD_DIR}"
350+
clang-tidy --version
351+
LOG_FILE="${BUILD_DIR}/opentelemetry-cpp-clang-tidy.log"
352+
cmake "${CMAKE_OPTIONS[@]}" \
353+
-S ${SRC_DIR} \
346354
-B ${BUILD_DIR} \
347-
-C ${SRC_DIR}/test_common/cmake/all-options-abiv2-preview.cmake \
348-
"${CMAKE_OPTIONS[@]}" \
355+
-C ${OTELCPP_CMAKE_CACHE_FILE_PATH} \
349356
-DWITH_OPENTRACING=OFF \
350357
-DCMAKE_CXX_FLAGS="-Wno-deprecated-declarations" \
351358
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
352-
-DCMAKE_CXX_CLANG_TIDY="clang-tidy;--quiet;-p;${BUILD_DIR}"
353-
make -j $(nproc)
354-
make test
359+
-DCMAKE_CXX_CLANG_TIDY="clang-tidy;--header-filter=.*/opentelemetry-cpp/.*;--exclude-header-filter=.*(internal/absl|third_party|third-party|build.*|/usr|/opt)/.*|.*\.pb\.h;--quiet"
360+
cmake --build "${BUILD_DIR}" -- -j $(nproc) 2>&1 | tee "$LOG_FILE"
361+
if [ ! -s "$LOG_FILE" ]; then
362+
echo "Error: Build log was not created at $LOG_FILE"
363+
exit 1
364+
fi
365+
echo "Build log written to: $LOG_FILE"
366+
echo "To generate a clang-tidy report, use the following command:"
367+
echo " python3 ./ci/create_clang_tidy_report.py --build_log $LOG_FILE"
355368
exit 0
356369
elif [[ "$1" == "cmake.legacy.exporter.otprotocol.test" ]]; then
357370
cd "${BUILD_DIR}"

0 commit comments

Comments
 (0)