Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ Checks: >
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-pro-*
-cppcoreguidelines-pro-*
11 changes: 8 additions & 3 deletions .devcontainer/Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
78 changes: 45 additions & 33 deletions .github/workflows/clang-tidy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ jobs:
matrix:
include:
- cmake_options: all-options-abiv1-preview
warning_limit: 63
warning_limit: 595
- cmake_options: all-options-abiv2-preview
warning_limit: 63
warning_limit: 597
env:
CC: /usr/bin/clang-18
CXX: /usr/bin/clang++-18
CXX_STANDARD: '14' # Run clang-tidy on the minimum supported c++ standard
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2
Expand Down Expand Up @@ -53,52 +57,60 @@ 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)"

- name: Prepare CMake
- name: Build and run clang-tidy
id: build
env:
CC: clang
CXX: clang++
OTELCPP_CMAKE_CACHE_FILE: ${{ matrix.cmake_options }}.cmake
BUILD_DIR: build-${{ matrix.cmake_options }}
run: |
echo "Running cmake..."
cmake -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;--quiet;-p;build-${{ matrix.cmake_options }}"
./ci/do_ci.sh cmake.clang_tidy.test
echo "build_log=${BUILD_DIR}/opentelemetry-cpp-clang-tidy.log" >> "$GITHUB_OUTPUT"

- name: Run clang-tidy
- name: Analyze clang-tidy output
id: analyze
run: |
cmake --build build-${{ matrix.cmake_options }} -- -j$(nproc) 2>&1 | tee clang-tidy-${{ matrix.cmake_options }}.log
SCRIPT_OUTPUT=$(python3 ./ci/create_clang_tidy_report.py \
--build_log ${{ steps.build.outputs.build_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

- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
- 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

184 changes: 184 additions & 0 deletions ci/create_clang_tidy_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# 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<file>.+):(?P<line>\d+):(?P<col>\d+): warning: (?P<msg>.+) "
r"\[(?P<check>.+)\]$"
)
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,
):
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 = "#### "
md.write(
f"{title} `clang-tidy` job reported {len(warnings)} warnings\n\n"
)

if not warnings:
return

def write_section(
title,
data,
item_sort_key,
header,
row_fmt,
group_key,
reverse,
summary_col_name,
):
md.write(f"<details><summary><b>{title}</b><i> - Click to expand </i></summary>\n\n")
sorted_groups = sorted(
data.items(), key=group_key, reverse=reverse
)

# Summary Table (Sorted by Count Descending)
summary_groups = sorted(
data.items(), key=lambda x: len(x[1]), reverse=True
)
md.write("#### Summary\n\n")
md.write(f"| {summary_col_name} | Count |\n|---|---|\n")
for key, items in summary_groups:
md.write(f"| {key} | {len(items)} |\n")
md.write("\n")

md.write("#### Details\n\n")
for key, items in sorted_groups:
md.write(
f"\n----\n\n**{key}** ({len(items)} warnings)\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")
md.write("</details>\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,
summary_col_name="File",
)

# 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,
summary_col_name="Check",
)

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",
)
args = parser.parse_args()

warnings = parse_log(args.build_log)
generate_report(warnings, args.output)

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()
31 changes: 22 additions & 9 deletions ci/do_ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ if [ -n "$CMAKE_TOOLCHAIN_FILE" ]; then
CMAKE_OPTIONS+=("-DCMAKE_TOOLCHAIN_FILE=$CMAKE_TOOLCHAIN_FILE")
fi

if [ -n "$OTELCPP_CMAKE_CACHE_FILE" ]; then
OTELCPP_CMAKE_CACHE_FILE_PATH="${SRC_DIR}/test_common/cmake/${OTELCPP_CMAKE_CACHE_FILE}"
else
OTELCPP_CMAKE_CACHE_FILE_PATH="${SRC_DIR}/test_common/cmake/all-options-abiv1-preview.cmake"
fi

echo "CMAKE_OPTIONS:" "${CMAKE_OPTIONS[@]}"

export CTEST_OUTPUT_ON_FAILURE=1
Expand Down Expand Up @@ -339,19 +345,26 @@ elif [[ "$1" == "cmake.legacy.test" ]]; then
make test
exit 0
elif [[ "$1" == "cmake.clang_tidy.test" ]]; then
cd "${BUILD_DIR}"
rm -rf *
export BUILD_ROOT="${BUILD_DIR}"
cmake -S ${SRC_DIR} \
rm -rf "${BUILD_DIR}"
mkdir -p "${BUILD_DIR}"
clang-tidy --version
LOG_FILE="${BUILD_DIR}/opentelemetry-cpp-clang-tidy.log"
cmake "${CMAKE_OPTIONS[@]}" \
-S ${SRC_DIR} \
-B ${BUILD_DIR} \
-C ${SRC_DIR}/test_common/cmake/all-options-abiv2-preview.cmake \
"${CMAKE_OPTIONS[@]}" \
-C ${OTELCPP_CMAKE_CACHE_FILE_PATH} \
-DWITH_OPENTRACING=OFF \
-DCMAKE_CXX_FLAGS="-Wno-deprecated-declarations" \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DCMAKE_CXX_CLANG_TIDY="clang-tidy;--quiet;-p;${BUILD_DIR}"
make -j $(nproc)
make test
-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"
cmake --build "${BUILD_DIR}" -- -j $(nproc) 2>&1 | tee "$LOG_FILE"
if [ ! -s "$LOG_FILE" ]; then
echo "Error: Build log was not created at $LOG_FILE"
exit 1
fi
echo "Build log written to: $LOG_FILE"
echo "To generate a clang-tidy report, use the following command:"
echo " python3 ./ci/create_clang_tidy_report.py --build_log $LOG_FILE"
exit 0
elif [[ "$1" == "cmake.legacy.exporter.otprotocol.test" ]]; then
cd "${BUILD_DIR}"
Expand Down
Loading