mirror of
https://gitlab.com/libeigen/eigen.git
synced 2026-04-10 11:34:33 +08:00
Add clang-tidy, codespell, and sanitizer checks to CI pipeline
libeigen/eigen!2178 Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
This commit is contained in:
130
ci/scripts/run-clang-tidy.sh
Executable file
130
ci/scripts/run-clang-tidy.sh
Executable file
@@ -0,0 +1,130 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Run clang-tidy on files changed in the current MR.
|
||||
#
|
||||
# Usage: run-clang-tidy.sh <base_sha> <build_dir>
|
||||
#
|
||||
# <base_sha> The merge-base commit to diff against.
|
||||
# <build_dir> Path to a CMake build directory containing compile_commands.json.
|
||||
#
|
||||
# For header files under Eigen/src/<Module>/, the script generates a minimal
|
||||
# driver .cpp that includes the parent module header so that
|
||||
# InternalHeaderCheck.h does not #error out.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BASE_SHA="${1:?Usage: run-clang-tidy.sh <base_sha> <build_dir>}"
|
||||
BUILD_DIR="${2:?Usage: run-clang-tidy.sh <base_sha> <build_dir>}"
|
||||
|
||||
if [ ! -f "${BUILD_DIR}/compile_commands.json" ]; then
|
||||
echo "ERROR: ${BUILD_DIR}/compile_commands.json not found."
|
||||
echo "Run cmake with -DCMAKE_EXPORT_COMPILE_COMMANDS=ON first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# External-dependency modules that require third-party headers we don't have.
|
||||
SKIP_MODULES="AccelerateSupport|CholmodSupport|KLUSupport|MetisSupport|PaStiXSupport|PardisoSupport|SPQRSupport|SuperLUSupport|UmfPackSupport"
|
||||
|
||||
# Get changed files (Added, Modified, Renamed).
|
||||
CHANGED_FILES=$(git diff --name-only --diff-filter=AMR "${BASE_SHA}" HEAD)
|
||||
|
||||
if [ -z "${CHANGED_FILES}" ]; then
|
||||
echo "No changed files to check."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "${TMPDIR}"' EXIT
|
||||
|
||||
ERRORS=0
|
||||
|
||||
# Map a header path under Eigen/src/<Module>/ to its module include.
|
||||
# e.g. Eigen/src/SVD/BDCSVD.h -> Eigen/SVD
|
||||
module_include_for_header() {
|
||||
local header="$1"
|
||||
local module
|
||||
|
||||
# Handle Eigen/src/<Module>/... -> Eigen/<Module>
|
||||
if [[ "${header}" =~ ^Eigen/src/([^/]+)/ ]]; then
|
||||
module="${BASH_REMATCH[1]}"
|
||||
# Handle unsupported/Eigen/src/<Module>/... -> unsupported/Eigen/<Module>
|
||||
elif [[ "${header}" =~ ^unsupported/Eigen/src/([^/]+)/ ]]; then
|
||||
module="unsupported/Eigen/${BASH_REMATCH[1]}"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Skip external-dependency modules.
|
||||
if [[ "${module}" =~ ^(${SKIP_MODULES})$ ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "${header}" =~ ^unsupported/ ]]; then
|
||||
echo "${module}"
|
||||
else
|
||||
echo "Eigen/${module}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Pick a compile command from compile_commands.json to use as a template.
|
||||
# We just need a valid set of compiler flags.
|
||||
TEMPLATE_CMD=$(python3 -c "
|
||||
import json, sys
|
||||
with open('${BUILD_DIR}/compile_commands.json') as f:
|
||||
cmds = json.load(f)
|
||||
# Pick the first .cpp entry.
|
||||
for c in cmds:
|
||||
if c['file'].endswith('.cpp'):
|
||||
print(c['directory'])
|
||||
break
|
||||
")
|
||||
|
||||
echo "Checking changed files with clang-tidy..."
|
||||
echo "Base SHA: ${BASE_SHA}"
|
||||
echo ""
|
||||
|
||||
for file in ${CHANGED_FILES}; do
|
||||
# Only check C++ source and header files.
|
||||
case "${file}" in
|
||||
*.cpp|*.cc|*.cxx)
|
||||
# Source file: run clang-tidy directly if it's in the compilation database.
|
||||
if grep -q "\"${file}\"" "${BUILD_DIR}/compile_commands.json" 2>/dev/null; then
|
||||
echo "=== ${file} ==="
|
||||
if ! clang-tidy -p "${BUILD_DIR}" "${file}" 2>&1; then
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
*.h|*.hpp)
|
||||
# Header file: generate a driver .cpp that includes the right module.
|
||||
MODULE_INCLUDE=$(module_include_for_header "${file}" || true)
|
||||
if [ -z "${MODULE_INCLUDE}" ]; then
|
||||
# Not a recognized module header or in skip list.
|
||||
continue
|
||||
fi
|
||||
|
||||
DRIVER="${TMPDIR}/tidy_driver_$(echo "${file}" | tr '/' '_').cpp"
|
||||
cat > "${DRIVER}" <<EOF
|
||||
#include <${MODULE_INCLUDE}>
|
||||
EOF
|
||||
|
||||
echo "=== ${file} (via ${MODULE_INCLUDE}) ==="
|
||||
if ! clang-tidy \
|
||||
-p "${BUILD_DIR}" \
|
||||
--header-filter="$(echo "${file}" | sed 's/[.[\*^$()+?{|]/\\&/g')" \
|
||||
"${DRIVER}" 2>&1; then
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ ${ERRORS} -gt 0 ]; then
|
||||
echo ""
|
||||
echo "clang-tidy reported issues in ${ERRORS} file(s)."
|
||||
exit 1
|
||||
else
|
||||
echo ""
|
||||
echo "clang-tidy: all clean."
|
||||
exit 0
|
||||
fi
|
||||
@@ -15,7 +15,8 @@ fi
|
||||
|
||||
set +x
|
||||
|
||||
ctest_cmd="ctest ${EIGEN_CI_CTEST_ARGS} --parallel ${NPROC} --output-on-failure --no-compress-output --build-noclean ${target}"
|
||||
EIGEN_CI_CTEST_PARALLEL=${EIGEN_CI_CTEST_PARALLEL:-${NPROC}}
|
||||
ctest_cmd="ctest ${EIGEN_CI_CTEST_ARGS} --parallel ${EIGEN_CI_CTEST_PARALLEL} --output-on-failure --no-compress-output --build-noclean ${target}"
|
||||
|
||||
echo "Running initial tests..."
|
||||
if ${ctest_cmd} -T test; then
|
||||
|
||||
Reference in New Issue
Block a user