diff --git a/scripts/lint/generate-compile-commands.sh b/scripts/lint/generate-compile-commands.sh new file mode 100644 index 000000000..77b5b4c70 --- /dev/null +++ b/scripts/lint/generate-compile-commands.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail +# Generate compile_commands.json by wrapping make with Bear. +# Usage: ./generate-compile-commands.sh + +BUILD_CMD=${1:-"make -j$(nproc)"} + +if ! command -v bear >/dev/null 2>&1; then + echo "bear is required. Install it (e.g. apt install bear)" >&2 + exit 1 +fi + +echo "Running: bear -- ${BUILD_CMD}" +rm -f compile_commands.json +bear -- ${BUILD_CMD} +echo "compile_commands.json generated" diff --git a/scripts/lint/normalize-clang-tidy.py b/scripts/lint/normalize-clang-tidy.py new file mode 100644 index 000000000..a83c313a7 --- /dev/null +++ b/scripts/lint/normalize-clang-tidy.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import sys +import yaml +import os + +""" +Simple normalizer for clang-tidy export-fixes YAML. Produces sorted text lines: +:: - +""" + +if len(sys.argv) != 2: + print("Usage: normalize-clang-tidy.py ") + sys.exit(2) + +path = sys.argv[1] +if not os.path.exists(path): + # No diagnostics + sys.exit(0) + +data = yaml.safe_load(open(path)) +diagnostics = [] +for diag in data.get('Diagnostics', []): + msg = diag.get('DiagnosticMessage', {}) + file = msg.get('FilePath', '') + offset = msg.get('FileOffset', 0) + # Best effort to map offset to line + try: + with open(file, 'rb') as fh: + content = fh.read() + line_no = content[:offset].count(b"\n") + 1 + except Exception: + line_no = 0 + check = diag.get('CheckName', '') + message = msg.get('Message', '').strip() + diagnostics.append(f"{file}:{line_no}: {check} - {message}") + +for l in sorted(set(diagnostics)): + print(l) diff --git a/scripts/lint/normalize-cppcheck.py b/scripts/lint/normalize-cppcheck.py new file mode 100644 index 000000000..3851220dc --- /dev/null +++ b/scripts/lint/normalize-cppcheck.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import sys +import re +import os + +""" +Normalize cppcheck stderr output. Produce lines like: +:: - +""" + +path = sys.argv[1] if len(sys.argv) > 1 else None +data = '' +if path and os.path.exists(path): + data = open(path).read() +else: + data = sys.stdin.read() + +lines = [] +for raw in data.splitlines(): + # cppcheck prints: [path:line]: (id) message + m = re.match(r"\[(?P[^:]+):(?P\d+)\] (?P[^:]+): (?P.*)", raw) + if m: + lines.append(f"{m.group('file')}:{m.group('line')}: {m.group('id').strip()} - {m.group('msg').strip()}") + +for l in sorted(set(lines)): + print(l) diff --git a/scripts/lint/run-local.sh b/scripts/lint/run-local.sh new file mode 100644 index 000000000..9eed1d469 --- /dev/null +++ b/scripts/lint/run-local.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Runs clang-tidy and cppcheck for the initial scope (lib/ and include/) +# Expectation: compile_commands.json exists in repo root (generated by generate-compile-commands.sh) + +SCOPE_GLOB="lib/**/*.cpp include/**/*.h" +CLANG_TIDY_BIN=${CLANG_TIDY_BIN:-clang-tidy} +CPPCHECK_BIN=${CPPCHECK_BIN:-cppcheck} + +if [ ! -f compile_commands.json ]; then + echo "compile_commands.json not found. Run scripts/lint/generate-compile-commands.sh first." >&2 + exit 1 +fi + +mkdir -p lint +echo "Running clang-tidy..." +# Export fixes to a YAML file per run; list files explicitly from git to scope to lib/ and include/ +FILES=$(git ls-files lib | tr '\n' ' ') +${CLANG_TIDY_BIN} -p . -checks='clang-analyzer-*,bugprone-*,performance-*,modernize-*,readability-*' --export-fixes=lint/clang-tidy-fixes.yaml ${FILES} || true + +echo "Running cppcheck..." +${CPPCHECK_BIN} --enable=warning,performance,portability,style --std=c++17 --project=compile_commands.json --inline-suppr 2> lint/cppcheck-output.txt || true + +echo "Normalizing outputs..." +python3 scripts/lint/normalize-clang-tidy.py lint/clang-tidy-fixes.yaml > lint/clang-tidy.txt || true +python3 scripts/lint/normalize-cppcheck.py lint/cppcheck-output.txt > lint/cppcheck.txt || true + +echo "Local lint run complete. Reports written to lint/clang-tidy.txt and lint/cppcheck.txt"