Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
67 changes: 46 additions & 21 deletions .github/workflows/clang-tidy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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)"

Expand All @@ -68,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;--quiet;-p;build-${{ matrix.cmake_options }}"
-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

172 changes: 172 additions & 0 deletions ci/create_clang_tidy_report.py
Original file line number Diff line number Diff line change
@@ -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<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,
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"<details><summary><b>{'Warnings breakdown'}</b><i> - Click to expand</i></summary>\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"<details><summary><b>{key} ({len(items)})</b></summary>"
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</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,
)

# 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"</details>\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()
13 changes: 10 additions & 3 deletions ci/do_ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -341,17 +341,24 @@ 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 \
"${CMAKE_OPTIONS[@]}" \
-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)
-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}"
Expand Down
Loading