#!/usr/bin/env bash
# Generate an HTML coverage report at coverage-html/index.html
# Run from anywhere; defaults: build dir = build-coverage, output = coverage-html/
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUILD_DIR="${COVERAGE_BUILD_DIR:-$ROOT/build-coverage}"
HTML_DIR="${COVERAGE_HTML_DIR:-$ROOT/coverage-html}"
INFO_FILE="${COVERAGE_INFO_FILE:-$ROOT/lcov.info}"
JOBS="${JOBS:-$(nproc)}"
need_cmd() {
command -v "$1" >/dev/null 2>&1 || {
echo "Error: required command not found: $1" >&2
exit 1
}
}
need_cmd cmake
need_cmd lcov
need_cmd genhtml
need_cmd gcc
# lcov defaults to gcov-11 on Ubuntu; use gcov matching the active compiler.
detect_gcov_tool() {
local ver
ver="$(gcc -dumpversion 2>/dev/null | cut -d. -f1)"
if [[ -n "$ver" ]] && command -v "gcov-${ver}" >/dev/null 2>&1; then
echo "gcov-${ver}"
else
echo "gcov"
fi
}
GCOV_TOOL="$(detect_gcov_tool)"
# lcov 1.14 (Ubuntu 22.04) supports different --ignore-errors vocabularies
# per tool: geninfo (gcov,source,graph), genhtml (source only), lcov also
# gcov,source,graph. lcov 1.15+ adds mismatch,unused,format,deprecated.
# Probe each tool's own help and pick the intersection with what we want.
detect_ignore_categories_for() {
local tool="$1"
local wanted=(gcov source graph mismatch unused)
local help_line
# Each tool prints "(gcov, source, graph)" style inside its --help.
help_line="$("$tool" --help 2>&1 | grep -m1 -- '--ignore-errors')"
local available=()
if [[ "$help_line" =~ \(([^\)]+)\) ]]; then
IFS=', ' read -r -a available <<<"${BASH_REMATCH[1]}"
fi
local supported=()
for w in "${wanted[@]}"; do
for a in "${available[@]}"; do
if [[ "$w" == "$a" ]]; then
supported+=("$w")
break
fi
done
done
# Fallback if parsing failed: source is the only one universally accepted.
if [[ ${#supported[@]} -eq 0 ]]; then
supported=(source)
fi
(IFS=,; echo "${supported[*]}")
}
LCOV_IGNORE=(--ignore-errors "$(detect_ignore_categories_for lcov)")
GENHTML_IGNORE=(--ignore-errors "$(detect_ignore_categories_for genhtml)")
configure_coverage_build() {
local -a gtest_args=()
if [[ -d "$ROOT/build/_deps/googletest-src" ]]; then
gtest_args+=(
"-DFETCHCONTENT_SOURCE_DIR_GOOGLETEST=$ROOT/build/_deps/googletest-src"
-DFETCHCONTENT_UPDATES_DISCONNECTED=ON
)
fi
cmake -B "$BUILD_DIR" \
-DCMAKE_BUILD_TYPE=Debug \
-DENABLE_COVERAGE=ON \
-DBUILD_TESTING=ON \
-DBUILD_APP=OFF \
-DBUILD_TOOLS=OFF \
-DBUILD_EXAMPLES=OFF \
-DWITH_QT=OFF \
-DWITH_PYTHON=OFF \
-DWITH_CERES=OFF \
-DWITH_G2O=OFF \
-DWITH_GTSAM=OFF \
-DWITH_MRPT=OFF \
-DWITH_VERTIGO=OFF \
-DWITH_CVSBA=OFF \
-DWITH_POINTMATCHER=OFF \
-DWITH_CCCORELIB=OFF \
-DWITH_OPEN3D=OFF \
-DWITH_LOAM=OFF \
-DWITH_FLOAM=OFF \
-DWITH_LIOSAM=OFF \
-DWITH_FLYCAPTURE2=OFF \
-DWITH_ZED=OFF \
-DWITH_ZEDOC=OFF \
-DWITH_REALSENSE=OFF \
"${gtest_args[@]}"
}
if [[ ! -f "$BUILD_DIR/CMakeCache.txt" ]]; then
echo "Configuring $BUILD_DIR (Debug + ENABLE_COVERAGE)..."
configure_coverage_build
elif ! grep -q '^ENABLE_COVERAGE:BOOL=ON' "$BUILD_DIR/CMakeCache.txt" 2>/dev/null; then
echo "Reconfiguring $BUILD_DIR with ENABLE_COVERAGE=ON..."
configure_coverage_build
fi
echo "Building $BUILD_DIR (gcov tool: $GCOV_TOOL)..."
cmake --build "$BUILD_DIR" -j"$JOBS"
echo "Clearing old coverage runtime data..."
find "$BUILD_DIR" -name '*.gcda' -delete
echo "Running tests..."
# Prepend coverage libs: otherwise LD_LIBRARY_PATH (e.g. ROS) can load
# librtabmap from another build tree and .gcda is never written for corelib/src.
(
export LD_LIBRARY_PATH="$BUILD_DIR/bin${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
cd "$BUILD_DIR" && ctest --output-on-failure
)
echo "Capturing coverage..."
lcov --gcov-tool "$GCOV_TOOL" "${LCOV_IGNORE[@]}" \
--capture \
--directory "$BUILD_DIR/corelib/src" \
--directory "$BUILD_DIR/utilite/src" \
--output-file "$INFO_FILE"
echo "Filtering coverage data (repo sources only)..."
lcov "${LCOV_IGNORE[@]}" --extract "$INFO_FILE" \
"${ROOT}/*" \
--output-file "$INFO_FILE"
lcov "${LCOV_IGNORE[@]}" --remove "$INFO_FILE" \
'*/sqlite3/*' \
'*/rtflann/*' \
--output-file "$INFO_FILE"
echo "Generating HTML..."
rm -rf "$HTML_DIR"
genhtml "${GENHTML_IGNORE[@]}" "$INFO_FILE" --output-directory "$HTML_DIR" --legend --demangle-cpp
echo ""
echo "Done: $HTML_DIR/index.html"
lcov --summary "$INFO_FILE" 2>/dev/null || true